How to Add New Properties to a Category

The Akeneo PIM allows the classification of products inside a customizable category tree.

Note

To implement this task you have to be comfortable with Symfony bundle overriding and Symfony services. This cookbook can be used to override other entities such as an attribute.

Add Non Translatable Properties to your own Category

Extend the category entity

The first step is to create a class that extends PIM Category class.

For example, we can add a description property with a text field.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /src/Acme/Bundle/CatalogBundle/Entity/Category.php
<?php

namespace Acme\Bundle\CatalogBundle\Entity;

use Pim\Bundle\CatalogBundle\Entity\Category as BaseCategory;

class Category extends BaseCategory
{
    protected $description;

    public function getDescription()
    {
        return $this->description;
    }

    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }
}

Configure the mapping override

Then, define the mapping for your own field only:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# /src/Acme/Bundle/CatalogBundle/Resources/config/doctrine/Category.orm.yml
Acme\Bundle\CatalogBundle\Entity\Category:
    type: entity
    table: pim_catalog_category
    changeTrackingPolicy: DEFERRED_EXPLICIT
    repositoryClass: Akeneo\Bundle\ClassificationBundle\Doctrine\ORM\Repository\CategoryRepository
    uniqueConstraints:
        pim_category_code_uc:
            columns:
                - code
    gedmo:
        tree:
            type: nested
    fields:
        description:
            type: string
            length: 255
            nullable: true

You also need to configure the mapping override in your application configuration (to avoid to copy/paste the whole parent class mapping):

# app/config/config.yml
akeneo_storage_utils:
    mapping_overrides:
        -
            original: Pim\Bundle\CatalogBundle\Entity\Category
            override: Acme\Bundle\CatalogBundle\Entity\Category

Define the Category Class

You need to update your category entity parameter used in entities.yml file:

1
2
3
# /src/Acme/Bundle/CatalogBundle/Resources/config/entities.yml
parameters:
    pim_catalog.entity.category.class: Acme\Bundle\CatalogBundle\Entity\Category

Important

If you are creating a new bundle, double check that this file is inside the extension

# /src/Acme/Bundle/CatalogBundle/DependencyInjection/AcmeAppExtension.php
public function load(array $configs, ContainerBuilder $container)
{
    /** ... **/
    $loader->load('entities.yml');
}

Note

You don’t have to add a lot of code to the Doctrine configuration to resolve target entities. We already have a resolver which injects the new category class name.

Define the Category Form

Firstly, you have to create your custom type by inheriting the CategoryType class and then add your custom fields:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /src/Acme/Bundle/EnrichBundle/Form/Type/CategoryType.php
<?php

namespace Acme\Bundle\EnrichBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;

use Pim\Bundle\EnrichBundle\Form\Type\CategoryType as BaseCategoryType;

/**
 * Type for category properties
 */
class CategoryType extends BaseCategoryType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder->add('description', 'text',
            [
                'required' => true
            ]
        );
    }
}

Then, you have to override the service definition of your form:

1
2
3
# /src/Acme/Bundle/EnrichBundle/Resources/config/form_types.yml
parameters:
    pim_enrich.form.type.category.class: Acme\Bundle\EnrichBundle\Form\Type\CategoryType

Then, add this new file to your dependency injection extension:

# /src/Acme/Bundle/EnrichBundle/DependencyInjection/AcmeAppExtension.php
public function load(array $configs, ContainerBuilder $container)
{
    /** ... **/
    $loader->load('form_types.yml');
}

Then, don’t forget to add your new field to the twig template:

# /src/Acme/Bundle/EnrichBundle/Resources/views/CategoryTree/Tab/property.html.twig
<!-- ... -->
{% set generalProperties %}
    {{ form_row(form.code) }}
    {{ form_row(form.description) }}
{% endset %}
<!-- ... -->

Make sure you’ve registered the template properly inside form_types.yml:

1
2
3
4
# /src/Acme/Bundle/EnrichBundle/Resources/config/form_types.yml
parameters:
    pim_enrich.form.type.category.class: Acme\Bundle\EnrichBundle\Form\Type\CategoryType
    pim_enrich.view_element.category.tab.property.template: 'AcmeEnrichBundle:CategoryTree:Tab/property.html.twig'

For the form validation you will have to add a new validation file:

1
2
3
4
5
# /src/Acme/Bundle/CatalogBundle/Resources/config/validation/category.yml
Acme\Bundle\CatalogBundle\Entity\Category:
    properties:
        description:
            - NotBlank: ~

Add Translatable Properties to your own Category

Extend the category entity and its translation entity

The first step is to create a class that extends PIM CategoryTranslation class.

For example, we can add an optional description property with a text field.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /src/Acme/Bundle/CatalogBundle/Entity/CategoryTranslation.php
<?php

namespace Acme\Bundle\CatalogBundle\Entity;

use Pim\Bundle\CatalogBundle\Entity\CategoryTranslation as BaseCategoryTranslation;

class CategoryTranslation extends BaseCategoryTranslation
{
    protected $description;

    public function getDescription()
    {
        return $this->description;
    }

    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }
}

Then we need to link this description to the Category class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /src/Acme/Bundle/CatalogBundle/Entity/Category.php
<?php

namespace Acme\Bundle\CatalogBundle\Entity;

use Pim\Bundle\CatalogBundle\Entity\Category as BaseCategory;

class Category extends BaseCategory
{
    public function getDescription()
    {
        $translated = ($this->getTranslation()) ? $this->getTranslation()->getDescription() : null;

        return ($translated !== '' && $translated !== null) ? $translated : '['.$this->getCode().']';
    }

    public function setDescription($description)
    {
        $this->getTranslation()->setDescription($description);

        return $this;
    }

    public function getTranslationFQCN()
    {
        return 'Acme\Bundle\CatalogBundle\Entity\CategoryTranslation';
    }
}

Configure the mapping override

Then, define the mapping for your own field only:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /src/Acme/Bundle/CatalogBundle/Resources/config/doctrine/CategoryCategoryTranslation.orm.yml
Acme\Bundle\CatalogBundle\Entity\CategoryTranslation:
    type: entity
    table: pim_catalog_category_translation
    changeTrackingPolicy: DEFERRED_EXPLICIT
    uniqueConstraints:
        locale_foreign_key_idx:
            columns:
                - locale
                - foreign_key
    fields:
        description:
            type: text
            nullable: true

As we override the Category class, we need to redefine its mapping too, even if we have nothing to add in it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# /src/Acme/Bundle/CatalogBundle/Resources/config/doctrine/Category.orm.yml
Acme\Bundle\CatalogBundle\Entity\Category:
    type: entity
    table: pim_catalog_category
    changeTrackingPolicy: DEFERRED_EXPLICIT
    repositoryClass: Akeneo\Bundle\ClassificationBundle\Doctrine\ORM\Repository\CategoryRepository
    uniqueConstraints:
        pim_category_code_uc:
            columns:
                - code
    gedmo:
        tree:
            type: nested

You also need to configure the mapping override in your application configuration (to avoid to copy/paste the whole parent class mapping):

# app/config/config.yml
akeneo_storage_utils:
    mapping_overrides:
        -
            original: Pim\Bundle\CatalogBundle\Entity\Category
            override: Acme\Bundle\CatalogBundle\Entity\Category
        -
            original: Pim\Bundle\CatalogBundle\Entity\CategoryTranslation
            override: Acme\Bundle\CatalogBundle\Entity\CategoryTranslation

Define the Category Class

You need to update your category entity parameter used in entities.yml file:

1
2
3
4
# /src/Acme/Bundle/CatalogBundle/Resources/config/entities.yml
parameters:
    pim_catalog.entity.category.class: Acme\Bundle\CatalogBundle\Entity\Category
    pim_catalog.entity.category_translation.class: Acme\Bundle\CatalogBundle\Entity\CategoryTranslation

Important

If you are creating a new bundle, double check that this file is inside the extension

# /src/Acme/Bundle/CatalogBundle/DependencyInjection/AcmeAppExtension.php
public function load(array $configs, ContainerBuilder $container)
{
    /** ... **/
    $loader->load('entities.yml');
}

Note

You don’t have to add a lot of code to the Doctrine configuration to resolve target entities. We already have a resolver which injects the new category class name.

Define the Category Form

Firstly, you have to create your custom type by inheriting the CategoryType class and then add your custom fields:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# /src/Acme/Bundle/EnrichBundle/Form/Type/CategoryType.php
<?php

namespace Acme\Bundle\EnrichBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;

use Pim\Bundle\EnrichBundle\Form\Type\CategoryType as BaseCategoryType;

/**
 * Type for category properties
 */
class CategoryType extends BaseCategoryType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);
        
        $builder->add(
            'description',
            'pim_translatable_field',
            [
                'field'             => 'description',
                'translation_class' => $this->translationdataClass,
                'entity_class'      => $this->dataClass,
                'property_path'     => 'translations',
                'widget'            => 'textarea',
            ]
        );
    }
}

Then, you have to override the service definition of your form:

1
2
3
# /src/Acme/Bundle/EnrichBundle/Resources/config/form_types.yml
parameters:
    pim_enrich.form.type.category.class: Acme\Bundle\EnrichBundle\Form\Type\CategoryType

Then, add this new file to your dependency injection extension:

# /src/Acme/Bundle/EnrichBundle/DependencyInjection/AcmeAppExtension.php
public function load(array $configs, ContainerBuilder $container)
{
    /** ... **/
    $loader->load('form_types.yml');
}

Then, don’t forget to add your new field to the twig template:

# /src/Acme/Bundle/EnrichBundle/Resources/views/CategoryTree/Tab/property.html.twig
<!-- ... -->
{% set nodeValues %}
    {{ form_row(form.label) }}
    {{ form_row(form.description) }}
{% endset %}
<!-- ... -->

Make sure you’ve registered the template properly inside form_types.yml:

1
2
3
4
# /src/Acme/Bundle/EnrichBundle/Resources/config/form_types.yml
parameters:
    pim_enrich.form.type.category.class: Acme\Bundle\EnrichBundle\Form\Type\CategoryType
    pim_enrich.view_element.category.tab.property.template: 'AcmeEnrichBundle:CategoryTree:Tab/property.html.twig'