Discuss your project
PHP symfony

Symfony 4 / Sonata: Initialize a Symfony 4 + Sonata + FosUser + Media project

Here are the commands to initialize a Symfony project with a secure admin panel.Start by installing the project skeleton.composer create-project symfony/skeleton project-name Add the necessary components: cd nom-du-projet composer require symfony/debug-pac...

Symfony 4 / Sonata: Initialize a Symfony 4 + Sonata + FosUser + Media project

Here are the commands to initialize a Symfony project with a secure admin panel.

Start by installing the project skeleton.
composer create-project symfony/skeleton project-name

Add the necessary components:

cd nom-du-projet
composer require symfony/debug-pack --no-update
composer require symfony/maker-bundle --dev --no-update
composer require sonata-project/doctrine-orm-admin-bundle --no-update
composer require templating --no-update
composer require symfony/translation --no-update
composer require doctrine/doctrine-fixtures-bundle --dev --no-update
composer require sonata-project/notification-bundle --no-update
composer require jms/serializer-bundle --no-update
composer require sonata-project/user-bundle --no-update
composer require sonata-project/doctrine-orm-admin-bundle --no-update
composer require symfony/serializer-pack --no-update
composer require swiftmailer-bundle --no-update
composer require symfony/apache-pack --no-update
composer require migrations --no-update
composer require symfony/var-dumper --no-update
composer require phpoffice/phpspreadsheet --no-update
composer require pixassociates/sortable-behavior-bundle --no-update
composer require sonata-project/media-bundle --no-update
composer require liip/imagine-bundle --no-update
composer require sonata-project/intl-bundle --no-update
composer require sonata-project/formatter-bundle  --no-update
composer require annotations  --no-update
composer update
composer dump-autoload

Then in /config/packages/framework.yaml add

framework:
    secret: '%env(APP_SECRET)%'
    #csrf_protection: true
    #http_method_override: true

    # Enables session support. Note that the session will ONLY be started if you read or write from it.
    # Remove or comment this section to explicitly disable session support.
    session:
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax

    #esi: true
    #fragments: true
    php_errors:
        log: true
    translator: { fallbacks: ['%locale%'] }
    serializer:
        enabled: true
    templating:
        engines: ['twig', 'php']

Then in /config/packages/doctrine.yaml add

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'
        types:
            json: Sonata\Doctrine\Types\JsonType

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '5.7'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            ApplicationSonataMediaBundle: ~
            SonataMediaBundle: ~
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

In /config/packages/sonata_media.yaml add

sonata_media:
    class:
        media: App\Application\Sonata\MediaBundle\Entity\Media
        gallery: App\Application\Sonata\MediaBundle\Entity\Gallery
        gallery_has_media: App\Application\Sonata\MediaBundle\Entity\GalleryHasMedia
    db_driver: doctrine_orm
    default_context: default
    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
    providers:
        file:
            allowed_extensions: ['pdf', 'txt', 'rtf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'odg', 'odp', 'ods', 'odc', 'odf', 'odb', 'odt', 'odp', 'csv', 'jpg', 'png', 'jpeg', 'svg']
            allowed_mime_types: ['application/pdf', 'application/vnd.openxmlformats-officedocument.presentationml.presentation',  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',  'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'application/vnd.ms-word.document.macroEnabled.12', 'application/vnd.ms-word.template.macroEnabled.12',  'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'application/vnd.ms-excel.sheet.macroEnabled.12','application/vnd.ms-excel.template.macroEnabled.12', 'application/vnd.ms-excel.addin.macroEnabled.12',  'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.presentationml.template',  'application/vnd.openxmlformats-officedocument.presentationml.slideshow',  'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',  'application/vnd.ms-powerpoint.template.macroEnabled.12', 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'application/vnd.ms-access', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/x-pdf', 'application/rtf', 'text/html', 'text/rtf', 'text/plain', 'application/vnd.ms-powerpoint', 'image/svg+xml', 'image/gif', 'image/jpeg', 'image/pjpeg', 'image/png']
            service:    sonata.media.provider.file
            resizer:    false
            filesystem: sonata.media.filesystem.local
            cdn:        sonata.media.cdn.server
            generator:  sonata.media.generator.default
            thumbnail:  sonata.media.thumbnail.liip_imagine
        image:
            allowed_extensions: ['gif', 'svg', 'jpg', 'jpeg', 'png']
            allowed_mime_types: ['image/svg+xml', 'image/gif', 'image/jpeg', 'image/pjpeg', 'image/png']
            #thumbnail: sonata.media.thumbnail.format          # default value
            #thumbnail: sonata.media.thumbnail.consumer.format # can be used to dispatch the resize action to async task
            thumbnail: sonata.media.thumbnail.liip_imagine    # use the LiipImagineBundle to resize the image


    resizer:
        simple:
            mode:  outbound

    pixlr:
        enabled:  false
        referrer: Sonata Project

liip_imagine:
     # valid drivers options include "gd" or "gmagick" or "imagick"
    driver: "imagick"
    
    filter_sets:
        test_banner:
            quality: 75
            filters:              
                relative_resize: { widen: 898,  heighten: 610 }
                crop: {size: [ 898, 610 ], start: [ 0, 0 ] }
                background : { size : [898, 610], position : center, color : '#FFFFFF' }
                
        test_thumb_gallery:
            quality: 100
            filters:
                thumbnail: { size: [288, 192], mode: outbound, allow_upscale: true}
                crop: {size: [288, 192], start: [ 0, 0 ] }
                background : { size : [288, 192], position : center, color : '#FFFFFF' }
                
        test_small_center:
            quality: 75
            filters:
                thumbnail: { size: [319, 207], mode: outbound, allow_upscale: true}
                crop: {size: [ 319, 207 ], start: [ 0, 0 ] }
                background : { size : [319, 207], position : center, color : '#FFFFFF' }
                
        test_medium_center_background:
            quality: 75
            filters:
                background : { size : [141, 93], position : center, color : '#FFFFFF' }
                thumbnail: { size: [141, 93], mode: outbound, allow_upscale: true}
                crop: {size: [ 141, 93 ], start: [ 0, 0 ] }
                
        test_widden:
            quality: 75
            filters:
                relative_resize: {widen: 796}

            

Add the formatter configuration

#config/packages/sonata_formatter.yml

sonata_formatter:
    default_formatter: richhtml
    formatters:
        markdown:
            service: sonata.formatter.text.markdown
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
        #        - sonata.media.formatter.twig #keep this commented unless you are using media bundle.


        text:
            service: sonata.formatter.text.text
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
        #        - sonata.media.formatter.twig


        rawhtml:
            service: sonata.formatter.text.raw
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
        #        - sonata.media.formatter.twig


        richhtml:
            service: sonata.formatter.text.raw
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
        #        - sonata.media.formatter.twig


        twig:
            service: sonata.formatter.text.twigengine
            extensions: [] # Twig formatter cannot have extensions
            
    ckeditor:
        templates:
            browser: '@App/Ckeditor/browser.html.twig'
            upload: '@App/Ckeditor/upload.html.twig'
            
            
fos_ck_editor:
    default_config: default
    configs:
        default:
            # default toolbar plus Format button
            toolbar:
            - [Bold, Italic, Underline, -, 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ,-, Cut, Copy, Paste, PasteText, PasteFromWord, -, Undo, Redo, -, NumberedList, BulletedList, -, Outdent, Indent, -,Blockquote, -, Image, Link, Unlink, Table]
            - [Format, Maximize, Source, Iframe]

           

And to finish the implementation of CKeditor, add the theme in twig

#twig.yaml
twig:
    default_path: '%kernel.project_dir%/templates'
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'
    exception_controller: null

    form_themes:
        - '@SonataFormatter/Form/formatter.html.twig'

Add the media bundle routes in config/routes.yaml

#index:
#    path: /
#    controller: App\Controller\DefaultController::index

gallery:
    resource: '@SonataMediaBundle/Resources/config/routing/gallery.xml'
    prefix: /media/gallery

media:
    resource: '@SonataMediaBundle/Resources/config/routing/media.xml'
    prefix: /media
    
#for 3.x change admin with admin_area    
admin:
    resource: "@SonataAdminBundle/Resources/config/routing/sonata_admin.xml"
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

Then in /config/routes/sonata_admin.yaml add

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    
    
admin_area:
    resource: "@SonataAdminBundle/Resources/config/routing/sonata_admin.xml"
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin    

Then in /config/packages/sonata_user.yaml add

sonata_user:
    security_acl: false
    manager_type: orm # can be orm or mongodb
    class:
        user: App\Application\Sonata\UserBundle\Entity\User
        group: App\Application\Sonata\UserBundle\Entity\Group
    

Then in /config/packages/security.yaml add

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt

    providers:
        in_memory: { memory: null }
        fos_userbundle:
            id: fos_user.user_provider.username

    firewalls:
        # Disabling the security for the web debug toolbar, the profiler and Assetic.
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        # -> custom firewall for the admin area of the URL
        admin:
            pattern:            /admin(.*)
            context:            user
            form_login:
                provider:       fos_userbundle
                login_path:     /admin/login
                use_forward:    false
                check_path:     /admin/login_check
                failure_path:   null
            logout:
                path:           /admin/logout
                target:         /admin/login
            anonymous:          true

        # -> end custom configuration

        # default login area for standard users

        # This firewall is used to handle the public login area
        # This part is handled by the FOS User Bundle
        main:
            pattern:             .*
            context:             user
            form_login:
                provider:       fos_userbundle
                login_path:     /login
                use_forward:    false
                check_path:     /login_check
                failure_path:   null
            logout:             true
            anonymous:          true
            
    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # Admin login page needs to be accessed without credential
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }

        # Secured part of the site
        # This config requires being logged for the whole site and having the admin role for the admin part.
        # Change these rules to adapt them to your needs
        - { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_SUPER_ADMIN] }
        - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

Then in /config/packages/swiftmailer.yaml add

swiftmailer:
    url: '%env(MAILER_URL)%'
    #pour envoyer directement un email sans attendre
    #spool: { type: 'memory' }
    #pour utiliser le spooler d'envois.
    #dans ce cas, inscrire une cron : 
    #* * * * *  cd /var/www/monprojet/ && APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=3
    spool:
        type: file
        path: '%kernel.project_dir%/var/spool'

Then in /config/packages/fos_user.yaml add

fos_user:
    db_driver:      orm # can be orm or odm
    firewall_name:  main
    #user_class:     Sonata\UserBundle\Entity\BaseUser
    user_class:     App\Application\Sonata\UserBundle\Entity\User

    group:
        #group_class:   Sonata\UserBundle\Entity\BaseGroup
        group_class:   App\Application\Sonata\UserBundle\Entity\Group
        group_manager: sonata.user.orm.group_manager # If you're using doctrine orm (use sonata.user.mongodb.group_manager for mongodb)

    service:
        user_manager: sonata.user.orm.user_manager

    from_email:
        address: "contact@partitech.com"
        sender_name: "contact@partitech.com"

Configure the locales to have the interface in French in /config/packages/translation.yaml

framework:
    default_locale: fr
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - en

Then configure the timezone:

#config/packages/sonata_intl.yaml
sonata_intl:
    timezone:
        default: Europe/Paris
        locales:
            fr:    Europe/Paris
            en_UK: Europe/London

Next, extend the bundle:

bin/console sonata:easy-extends:generate SonataUserBundle --dest=src --namespace_prefix=App

You must also generate the entity for the media bundle

bin/console sonata:easy-extends:generate --dest=src SonataMediaBundle --namespace_prefix=App

And register it in the bundles to load (/config/bundles.php) add the call to ApplicationSonataUserBundle:

<?php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Sonata\DatagridBundle\SonataDatagridBundle::class => ['all' => true],
    Sonata\CoreBundle\SonataCoreBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Sonata\BlockBundle\SonataBlockBundle::class => ['all' => true],
    Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
    Sonata\AdminBundle\SonataAdminBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle::class => ['all' => true],
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
    Sonata\EasyExtendsBundle\SonataEasyExtendsBundle::class => ['all' => true],
    Sonata\NotificationBundle\SonataNotificationBundle::class => ['all' => true],
    FOS\UserBundle\FOSUserBundle::class => ['all' => true],
    Sonata\UserBundle\SonataUserBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
    Pix\SortableBehaviorBundle\PixSortableBehaviorBundle::class => ['all' => true],
    Sonata\MediaBundle\SonataMediaBundle::class => ['all' => true],
    App\Application\Sonata\UserBundle\ApplicationSonataUserBundle::class => ['all' => true],
    App\Application\Sonata\MediaBundle\ApplicationSonataMediaBundle::class => ['all' => true],
Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true],
Sonata\IntlBundle\SonataIntlBundle::class => ['all' => true],
];

Exclude the Application directory from the autowiring of services

# config/services.yaml

services:
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Tests,Application}'

Last points, configure the database in the .env file

DATABASE_URL=mysql://root:toor@127.0.0.1:3306/delef?serverVersion=5.7

Start the creation of the tables:

php bin/console doctrine:schema:update --force

Install the assets:

php bin/console assets:install
php bin/console ckeditor:install

Create the file storage directory

mkdir -p public/uploads/media

Clear the cache:

php bin/console cache:clear

And create the admin user

php bin/console fos:user:create admin mail@mail.com p@ssword --super-admin

Adapt the example to the installed version

The Symfony and Sonata examples remain tied to the versions indicated in the article. Symfony 4.x and Symfony 6.2 are no longer maintained: for a current project, use a maintained branch and the documentation that exactly matches your dependencies.

php bin/console about
composer show symfony/framework-bundle sonata-project/admin-bundle

Before resuming the code, check the signatures, services, routes, and templates concerned. Then add a functional test covering permissions, HTTP code, and the visible result.

Reference: maintained Symfony versions.

Share this article