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.