Symfony 6 / Sonata 5 : Initialiser un projet Symfony 6 + Sonata admin 5 + UserBundle + MediaBundle

Voici les commandes pour initialiser un projet Symfony avec un admin sécurisé.

Pour faire tourner Symfony 6 nous devrons installer php8.0 ou php8.1

Pour php8.0

sudo apt-get install php8.0-cli libapache2-mod-php8.0 php8.0-common php8.0-opcache php8.0-igbinary php8.0-imagick  php8.0-msgpack php8.0-readline  php8.0-memcached  php8.0-xml php8.0-mbstring php8.0-gd php8.0-mysql php8.0-curl php8.0-intl php8.0-memcache php8.0-memcached  memcached libapache2-mod-php8.0 php8.0-zip php8.0-mysql

Pour php8.1

sudo apt-get install php8.1-cli libapache2-mod-php8.1 php8.1-common php8.1-opcache php8.1-igbinary php8.1-imagick  php8.1-msgpack php8.1-readline  php8.1-memcached  php8.1-xml php8.1-mbstring php8.1-gd php8.1-mysql php8.1-curl php8.1-intl php8.1-memcache php8.1-memcached  memcached libapache2-mod-php8.1 php8.1-zip php8.1-mysql

Ensuite on installe le skeleton du projet.

php8.1 composer.phar create-project symfony/skeleton:"6.0.x-dev" skeleton-sf6
cp composer.phar skeleton-sf6/
cd skeleton-sf6
php8.1 composer.phar require webapp

On vérifie que tout fonctionne bien à ce stade en lançant le serveur web intégré à php.

php8.1 -S localhost:8000 -t public

Ce qui nous donne l’url suivante dans notre navigateur : http://localhost:8000/

Selection_249
Symfony 6.0.6

On installe ensuite user-bundle qui nous est nécessaire dans l’admin Sonata. Il va nous installer toutes les dépendances nécessaires, y compris Sonata/admin.
On prend soin de downgrader Syfony/translation qui est trop récent dans notre skeleton pour Sonata à ce jour.

php8.1 composer.phar require symfony/translation-contracts:2.5
php8.1 composer.phar require sonata-project/user-bundle:5.x-dev
php8.1 bin/console assets:install

On se heurte à un premier problème :

 The child config "resetting" under "sonata_user" must be configured. 

Il faut pour cela ajouter une configuration par défaut dans /config/packages/sonata_user.yaml

#####/config/packages/sonata_user.yaml
sonata_user:
    class:
        user: App\Entity\User
    resetting:
        email:
            address: "test@test.com"
            sender_name: Backoffice

Et faire un console/bin cache:clear

php8.1 bin/console cache:clear

On installe ensuite Sonata/admin. En installant doctrine-orm-admin-bundle on va installer automatiquement admin-bundle en évitant les conflits.

php8.1 composer.phar require sonata-project/doctrine-orm-admin-bundle
php8.1 bin/console assets:install


On contrôle notre site depuis le serveur web.

php8.1 -S localhost:8000 -t public

On devrait avoir l’erreur suivante :

An exception has been thrown during the rendering of a template ("Asset manifest file "/public/build/manifest.json" does not exist.").
Il faudra installer webpack
yarn add --dev @symfony/webpack-encore
yarn add webpack-notifier --dev
yarn encore dev

Sonata admin est correctement installé. L’authentification n’est pas encore configurée et il nous manque encore la gestion des médias et les classifications (on verra les classifications dans un article suivant).

image

On installe et configure les ACL.

php8.1 composer.phar require symfony/acl-bundle

On configure sonata_user.yml

#####/config/packages/sonata_user.yaml

sonata_user:
    class:
        user: App\Entity\User
    resetting:
        email:
            address: "test@test.com"
            sender_name: Backoffice
            
    security_acl: true
    manager_type: orm # can be orm or mongodb


On configure security.yaml

#####/config/packages/security.yaml
security:
    enable_authenticator_manager: true
    password_hashers:
        Sonata\UserBundle\Model\UserInterface:
            algorithm: auto
    providers:
        sonata_user_bundle:
            id: sonata.user.security.user_provider
    access_decision_manager:
        strategy: unanimous   
    
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin:
            lazy: true
            pattern: /admin(.*)
            provider: sonata_user_bundle
            context: user
            switch_user: true
            form_login:
                login_path: sonata_user_admin_security_login
                check_path: sonata_user_admin_security_check
                default_target_path: sonata_admin_dashboard
            logout:
                path: sonata_user_admin_security_logout
                target: sonata_user_admin_security_login
            remember_me:
                #secret: "%env(APP_SECRET)%"
                secret: "123456"
                lifetime: 2629746
                path: /admin


    
    access_control:
        - { path: ^/admin/login$, role: PUBLIC_ACCESS }
        - { path: ^/admin/logout$, role: PUBLIC_ACCESS }
        - { path: ^/admin/login_check$, role: PUBLIC_ACCESS }
        - { path: ^/admin/request$, role: PUBLIC_ACCESS }
        - { path: ^/admin/check-email$, role: PUBLIC_ACCESS }
        - { path: ^/admin/reset/.*$, role: PUBLIC_ACCESS }
        - { path: ^/admin/, role: ROLE_ADMIN }    
    
    role_hierarchy:
        ROLE_ADMIN:
            - ROLE_USER
            - ROLE_SONATA_ADMIN
            - ROLE_SONATA_USER_ADMIN_USER_VIEW
        ROLE_SUPER_ADMIN:
            - ROLE_ADMIN
            - ROLE_ALLOWED_TO_SWITCH        
   

On ajoute les routes de l’admin.

#####/config/route.yaml
sonata_user_admin_security:
    resource: '@SonataUserBundle/Resources/config/routing/admin_security.xml'
    prefix: /admin

sonata_user_admin_resetting:
    resource: '@SonataUserBundle/Resources/config/routing/admin_resetting.xml'
    prefix: /admin/resetting

On créé notre entité user.

#/src/Entity/User.php
<?php 
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\UserBundle\Entity\BaseUser;

/**
 * @ORM\Entity
 * @ORM\Table(name="user__user")
 */
class User extends BaseUser
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    protected $id;
}

On met à jour le schéma de notre base de données et on crée notre utilisateur admin.

php8.1 bin/console doctrine:schema:update --force
php8.1 bin/console sonata:user:create admin admin@test.com admin123456
php8.1 bin/console sonata:user:promote --super-admin admin

On relance notre serveur et on teste l’url : http://localhost:8000/admin/dashboard

php8.1 -S localhost:8000 -t public

On est bien redirigé vers la mire de login.

Selection_260

L’utilisateur est bien autorisé à rentrer dans l’admin.

image-1

On configure maintenant la gestion des médias.

On crée 3 entités : Gallery, GalleryItem, et Media dans une structure de répertoires à part pour ne pas polluer notre application. On fera la même chose pour UserBundle et ClassificationBundle.
Nous allons tout placer dans src/Application/Sonata/MediaBundle/Entity

<?php

declare(strict_types=1);

namespace App\Application\Sonata\MediaBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\MediaBundle\Entity\BaseGallery;

/**
 * @phpstan-extends BaseGallery<GalleryItem>
 *
 * @ORM\Entity
 * @ORM\Table(name="media__gallery")
 */
class Gallery extends BaseGallery
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    public function getId(): ?int
    {
        return $this->id;
    }
}
<?php

declare(strict_types=1);

namespace App\Application\Sonata\MediaBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\MediaBundle\Entity\BaseGalleryItem;

/**
 * @ORM\Entity
 * @ORM\Table(name="media__gallery_media")
 */
class GalleryItem extends BaseGalleryItem
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    public function getId(): ?int
    {
        return $this->id;
    }
}
<?php

declare(strict_types=1);

namespace App\Application\Sonata\MediaBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\MediaBundle\Entity\BaseMedia;
use App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationCategory as SonataClassificationCategory;

/**
 * @ORM\Entity
 * @ORM\Table(name="media__media")
 */
class Media extends BaseMedia
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;
    
    /**
     * @var SonataClassificationCategory|null
     */
    protected ?object $category = null;

    public function getId(): ?int
    {
        return $this->id;
    }
    
    
    public function getCategory(): ?object
    {
        return $this->category;
    }
    
    public function setCategory(?object $category = null): void
    {
        $this->category = $category;
    }
}

On ajoute la configuration dans 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
    default_context: default
    db_driver: doctrine_orm
    providers:
        file:
            allowed_extensions: [jpg, png, jpeg, pdf, ogv, mp4, webm]
            allowed_mime_types:
                - image/pjpeg
                - image/jpeg
                - image/png
                - image/x-png
                - application/pdf
                - application/x-pdf
                - application/ogg
                - video/mp4
                - video/webm

    contexts:
        default:
            providers:
                - sonata.media.provider.dailymotion
                - sonata.media.provider.youtube
                - sonata.media.provider.image
                - sonata.media.provider.file
                - sonata.media.provider.vimeo

            formats:
                small: { width: 100 , quality: 70}
                big:   { width: 500 , quality: 70}

    cdn:
        server:
            path: /upload/media

    filesystem:
        local:
            # Directory for uploads should be writable
            directory: "%kernel.project_dir%/public/upload/media"
            create: false

On ajoute notre structure de surcharge de MediaBundle dans 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\MediaBundle:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Application/Sonata/MediaBundle/Entity'
                prefix: 'App\Application\Sonata\MediaBundle\Entity'
                alias: App\Application\Sonata\MediaBundle
 
        filters:
            softdeleteable:
                class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter

On met à jour le schéma de la base de données.

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

On configure notre sonata_admin.yml par défaut.

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:
            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

Et tout fonctionne.

image-2
image-3