Symfony 6 / Sonata 5: Installazione di SonataClassification.

Abbiamo visto in precedenti articoli come inizializzare un progetto Symfony 6 + Sonata 5 con media e utenti in un'amministrazione sicura. Abbiamo poi esaminato come gestire le localizzazioni degli utenti.
Ora vedremo come installare e gestire le classificazioni.

Procediamo con l'installazione dell'ultima versione disponibile al momento.

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

Sembra che ci sia un errore di configurazione.

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

Rimuoviamo la sezione dei media dalla configurazione sonata_classification.yaml

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

In seguito incontriamo un nuovo errore.

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

Non è un grosso problema; configureremo tutto affinché funzioni al primo tentativo.
Prima implementeremo le nostre entità in una directory diversa da src/app per non ingombrare la nostra applicazione con le sovraccariche dai nostri bundle. Proprio come per UserBundle e MediaBundle, le posizioniamo in src/Application/Sonata/ClassificationBundle/Entity e creeremo i nostri 4 file.

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;
    }
}

In seguito, dobbiamo fornire i percorsi delle nostre classi 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
   

Dobbiamo inoltre aggiungere il percorso della classe categoria alle definizioni dei media nel file config/packages/sonata_media.yaml

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

Aggiungiamo il nostro 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

Creiamo lo schema del database.

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

Aggiungiamo le nostre interfacce all'amministrazione nel file config/packages/sonata_admin.yaml

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
                    

E tutto dovrebbe funzionare normalmente.

Selection_285

Lanciamo un comando di migrazione per aggiornare tutti gli elementi con il contesto predefinito.
Sembra che non abbiamo molti elementi da migrare in questa fase.

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