Symfony 6 / Sonata 5: Installing SonataClassification.

We have seen in previous articles how to initialize a Symfony 6 + Sonata 5 project with media and users in a secure admin. We then looked at how to manage user localizations.
Now we will see how to install and manage classifications.

We proceed with the installation of the latest version to date.

php8.1 composer.phar require  sonata-project/classification-bundle:5.x-dev

Apparently, we have a configuration error.

Unrecognized option "media" under "sonata_classification.class". Available   
!!    options are "category", "collection", "context", "tag".      

We remove the media section from the sonata_classification.yaml configuration

sonata_classification:
    class:
        category: App\Entity\SonataClassificationCategory
        collection: App\Entity\SonataClassificationCollection
        context: App\Entity\SonataClassificationContext
        tag: App\Entity\SonataClassificationTag
        #media: App\Entity\SonataMediaMedia

We then encounter a new error.

PHP Fatal error:  Type of App\Entity\SonataClassificationCategory::$children must be Doctrine\Common\Collections\Collection (as in class Sonata\ClassificationBundle\Model\Category) in /src/Entity/SonataClassificationCategory.php on line 18

It's not a big deal; we will configure everything so that it works on the first try.
We will first implement our entities in a directory other than src/app in order not to clutter our application with the overloads from our bundles. Just like UserBundle and MediaBundle, we put them in src/Application/Sonata/ClassificationBundle/Entity and we will create our 4 files.

src/Application/Sonata/ClassificationBundle/Entity/SonataClassificationCategory.php

<?php
declare(strict_types=1);

namespace App\Application\Sonata\ClassificationBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseCategory;

/**
 * @ORM\Entity
 * @ORM\Table(name="classification__category")
 */
class SonataClassificationCategory extends BaseCategory
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    protected $id;
    
    public function getId(): ?int
    {
        return $this->id;
    }
}
?>


src/Application/Sonata/ClassificationBundle/Entity/SonataClassificationCollection.php

<?php 
declare(strict_types=1);

namespace App\Application\Sonata\ClassificationBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseCollection;

/**
 * @ORM\Entity
 * @ORM\Table(name="classification__collection")
 */
class SonataClassificationCollection extends BaseCollection
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    protected $id;
    
    public function getId(): ?int
    {
        return $this->id;
    }
}


?>

src/Application/Sonata/ClassificationBundle/Entity/SonataClassificationContext.php

<?php 
declare(strict_types=1);

namespace App\Application\Sonata\ClassificationBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseContext;

/**
 * @ORM\Entity
 * @ORM\Table(name="classification__context")
 */
class SonataClassificationContext extends BaseContext
{
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="string", length=64, nullable=false)
     */
     protected ?string $id = null;
    
    public function getId(): ?string
    {
        return $this->id;
    }
}

?>

src/Application/Sonata/ClassificationBundle/Entity/SonataClassificationTag.php

<?php
declare(strict_types=1);

namespace App\Application\Sonata\ClassificationBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseTag;

/**
 * @ORM\Entity
 * @ORM\Table(name="classification__tag")
 */
class SonataClassificationTag extends BaseTag
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    protected $id;
    
    public function getId(): ?int
    {
        return $this->id;
    }
}

Next, we need to provide the paths of our classes in config/packages/sonata_classification.yaml

sonata_classification:
    class:
        category: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationCategory
        collection: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationCollection
        context: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationContext
        tag: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationTag
   

We also need to add the category class path to the media definitions in the config/packages/sonata_media.yaml file

sonata_media:
    class:
        media: App\Application\Sonata\MediaBundle\Entity\Media
        gallery: App\Application\Sonata\MediaBundle\Entity\Gallery
        gallery_item: App\Application\Sonata\MediaBundle\Entity\GalleryItem
        category: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationCategory

We add our bundle in config/packages/doctrine.yaml

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '13'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App
            App\Application\Sonata\UserBundle:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Application/Sonata/UserBundle/Entity'
                prefix: 'App\Application\Sonata\UserBundle\Entity'
                alias: App\Application\Sonata\UserBundle
            App\Application\Sonata\MediaBundle:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Application/Sonata/MediaBundle/Entity'
                prefix: 'App\Application\Sonata\MediaBundle\Entity'
                alias: App\Application\Sonata\MediaBundle
            App\Application\Sonata\ClassificationBundle:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Application/Sonata/ClassificationBundle/Entity'
                prefix: 'App\Application\Sonata\ClassificationBundle\Entity'
                alias: App\Application\Sonata\ClassificationBundle
                
            translatable:
                type: annotation # or attribute
                alias: Gedmo
                prefix: Gedmo\Translatable\Entity
                # make sure vendor library location is correct
                dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Translatable/Entity"
        filters:
            softdeleteable:
                class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter

We create the database schema.

 php8.1 bin/console doctrine:schema:update --force
Selection_280

We add our interfaces to the admin in the config/packages/sonata_admin.yaml file

sonata_admin:
    title: Backoffice
    title_logo: /bundles/sonataadmin/images/logo_title.png
    show_mosaic_button: false
    security:
        handler: sonata.admin.security.handler.role
    options:
        default_admin_route: edit
        html5_validate: false
    global_search:
        admin_route: edit
    breadcrumbs:
        child_admin_route: edit
    dashboard:
        groups:
            runroom:
#                label: Base
#                items:
#                    - Runroom\SeoBundle\Admin\MetaInformationAdmin
#                    - Runroom\BasicPageBundle\Admin\BasicPageAdmin
#                    - Runroom\TranslationBundle\Admin\TranslationAdmin
#                    - Runroom\RedirectionBundle\Admin\RedirectAdmin
#                    - label: Cookies
#                      route: admin_runroom_cookies_cookiespage_edit
#                      route_params: { id: 1 }
            users:
                label: Users
                icon: <i class="fa fa-users"></i>
                on_top: true
                items:
                    - sonata.user.admin.user
            media:
                label: Media
                icon: <i class="fa fa-photo"></i>
                on_top: true
                items:
                    - sonata.media.admin.media
                    
            classification:
                label: Classification
                icon: <i class="fa fa-tags"></i>
                items:
                    - sonata.classification.admin.category
                    - sonata.classification.admin.tag
                    - sonata.classification.admin.collection
                    - sonata.classification.admin.context
                    

And everything should work normally.

Selection_285

We run a migration command to update all elements with the default context.
Apparently, we don't have many elements to migrate at this stage.

php8.1 bin/console sonata:classification:fix-context
Selection_282