Symfony 6 / Sonata 5: Instalando SonataClassification.

Hemos visto en artículos anteriores cómo inicializar un proyecto Symfony 6 + Sonata 5 con multimedia y usuarios en un admin seguro. Luego, analizamos cómo gestionar las localizaciones de usuarios.
Ahora veremos cómo instalar y gestionar clasificaciones.

Procedemos con la instalación de la última versión hasta la fecha.

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

Al parecer, tenemos un error de configuración.

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

Eliminamos la sección de medios del archivo de configuración 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

Luego nos encontramos con un nuevo 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

No es un gran problema; configuraremos todo para que funcione a la primera.
Primero implementaremos nuestras entidades en un directorio distinto a src/app para no llenar nuestra aplicación con las sobrecargas de nuestros bundles. Al igual que con UserBundle y MediaBundle, los colocamos en src/Application/Sonata/ClassificationBundle/Entity y crearemos nuestros 4 archivos.

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

Luego, necesitamos proporcionar las rutas de nuestras clases en 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
   

También necesitamos agregar la ruta de la clase de categoría a las definiciones de multimedia en el archivo 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

Agregamos nuestro bundle en 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

Creamos el esquema de la base de datos.

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

Agregamos nuestras interfaces al admin en el archivo 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
                    

Y todo debería funcionar normalmente.

Selection_285

Ejecutamos un comando de migración para actualizar todos los elementos con el contexto predeterminado.
Al parecer, no tenemos muchos elementos para migrar en esta etapa.

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