Установка и настройка Sonata Admin на Symfony 4

от автора

Приветствую всех. В данной статье поговорим об Symfony 4 и Sonata Admin.

Эта статья будет скорей шпаргалкой для меня, потому что когда мне понадобилось
установить Sonata Admin на Symfony 4, то я столкнулся с массой неточностей
в документации и сама документация была раскидана по нескольким местам.

Здесь я рассмотрю весь процесс, начиная от создания проекта и заканчивая авторизацией вместе с аутентификацией. Отдельные части настроек взяты из официальной документации, часть бралась из комментариев на GitHub, где обсуждались проблемы при установке.

Также расписаны возможные подводные камни и пути их обхода.

Создаем проект на symfony

$ composer create-project symfony/skeleton sonatademo

$ cd sonatademo

Запускаем встроенный веб сервер Symfony.

$ symfony serve

Переходим по ссылке http://127.0.0.1:8000/ и получим стандартное приветствие Symfony. Значит все работает корректно.

Устанавливаем Sonata Admin

$ composer require sonata-project/admin-bundle

Для того, чтобы взаимодействовать с базой данных нужно установить одну из трех библиотек:

В данной статья я использую SonataDoctrineORMAdminBundle, что более чем достаточно для работы с MySQL или Sqlite.

Устанавливаем SonataDoctrineORMAdminBundle.

$ composer require sonata-project/doctrine-orm-admin-bundle

Теперь настроим работу с Sqlite.

Открываем .env файл и в секции ###> doctrine/doctrine-bundle ### меняем DATABASE_URL.

DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"

Так как Symfony Flex делает почти все работу за нас при установке, то остается сделать несколько манипуляций для получения конечного результата.

# config/packages/framework.yaml framework:     translator: { fallbacks: ['%locale%'] }

Выполняем

$ composer dump-env dev

Теперь переходим по ссылке http://127.0.0.1:8000/admin и видим пустой стандартный административный интерфейс.

Создание сущностей

Создадим две сущности, связанные между собой связью один ко многим.

<?php // src/Entity/City.php namespace App\Entity;  use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM;  /**  * @ORM\Entity  */ class City {     /**      * @ORM\Id()      * @ORM\GeneratedValue()      * @ORM\Column(type="integer", options={"unsigned":true})      */     private $id;      /**      * @var string      *      * @ORM\Column(type="string")      */     private $title;      /**      * @var string      *      * @ORM\Column(type="text")      */     private $description;      /**      * @var bool      *      * @ORM\Column(type="boolean")      */     private $isBig = false;      /**      * @ORM\OneToMany(targetEntity="Address", mappedBy="city")      */     private $addresses;      public function __construct()     {         $this->addresses = new ArrayCollection();     }      public function getAddresses()     {         return $this->addresses;     }      /**      * @return string      */     public function getTitle(): ?string     {         return $this->title;     }      /**      * @param string $title      * @return City      */     public function setTitle(string $title): City     {         $this->title = $title;         return $this;     }      /**      * @return string      */     public function getDescription(): ?string     {         return $this->description;     }      /**      * @param string $description      * @return City      */     public function setDescription(string $description): City     {         $this->description = $description;         return $this;     }      /**      * @return bool      */     public function isBig(): ?bool     {         return $this->isBig;     }      /**      * @param bool $isBig      * @return City      */     public function setIsBig(bool $isBig): City     {         $this->isBig = $isBig;         return $this;     }      public function __toString()     {         return $this->title;     } }

<?php // src/Entity/Address.php namespace App\Entity;  use Doctrine\ORM\Mapping as ORM;  /**  * @ORM\Entity  */ class Address {     /**      * @ORM\Id()      * @ORM\GeneratedValue()      * @ORM\Column(type="integer", options={"unsigned":true})      */     private $id;      /**      * @var string      *      * @ORM\Column(type="string")      */     private $title;      /**      * @var string      *      * @ORM\Column(type="text")      */     private $description;      /**      * @ORM\ManyToOne(targetEntity="City", inversedBy="addresses")      */     private $city;      /**      * @return string      */     public function getTitle(): ?string     {         return $this->title;     }      /**      * @param string $title      * @return Address      */     public function setTitle(string $title): Address     {         $this->title = $title;         return $this;     }      /**      * @return string      */     public function getDescription(): ?string     {         return $this->description;     }      /**      * @param string $description      * @return Address      */     public function setDescription(string $description): Address     {         $this->description = $description;         return $this;     }      /**      * @return City      */     public function getCity(): ?City     {         return $this->city;     }      /**      * @param City $city      * @return Address      */     public function setCity(City $city)     {         $this->city = $city;         return $this;     }      public function __toString()     {         return $this->title;     } }

Синхронизируемся с БД.

bin/console doctrine:schema:create

Настройка сущностей для Sonata Admin

Нужно для каждой сущности создать отдельный файл с описанием,
как Sonata Admin должна работать.

<?php  // src/Admin/CityAdmin.php  namespace App\Admin;  use App\Entity\Address; use App\Entity\City; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Form\Type\CollectionType; use Sonata\AdminBundle\Form\Type\ModelType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;  final class CityAdmin extends AbstractAdmin {     protected function configureFormFields(FormMapper $formMapper)     {         $formMapper->add('title', TextType::class);         $formMapper->add('description', TextareaType::class);         $formMapper->add('isBig', CheckboxType::class);     }      protected function configureDatagridFilters(DatagridMapper $datagridMapper)     {         $datagridMapper->add('title');         $datagridMapper->add('isBig');     }      protected function configureListFields(ListMapper $listMapper)     {         $listMapper->addIdentifier('title');         $listMapper->addIdentifier('isBig');     } }

<?php  // src/Admin/AddressAdmin.php  namespace App\Admin;  use App\Entity\City; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Form\Type\ModelType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;  final class AddressAdmin extends AbstractAdmin {     protected function configureFormFields(FormMapper $formMapper)     {         $formMapper->add('title', TextType::class);         $formMapper->add('description', TextareaType::class);         $formMapper->add('city', ModelType::class, [             'class' => City::class,             'property' => 'title',         ]);     }      protected function configureDatagridFilters(DatagridMapper $datagridMapper)     {         $datagridMapper->add('title');     }      protected function configureListFields(ListMapper $listMapper)     {         $listMapper->addIdentifier('title');     } }

Эти классы нужно описать в service.yaml.

# config/service.yaml services:     ...     App\Admin\CityAdmin:         arguments: [~, App\Entity\City, ~]         tags:             - { name: sonata.admin, manager_type: orm, label: City }     App\Admin\AddressAdmin:         arguments: [~, App\Entity\Address, ~]         tags:             - { name: sonata.admin, manager_type: orm, label: Address }

Если при заходе в административную часть возникают ошибки, например
Unable to generate a URL for the named route, то нужно почистить кэш приложения
и попробовать снова.

bin/console cache:clear

Теперь, если перейти по ссылке http://127.0.0.1:8000/admin увидим
список из двух элементов Address и City, где можно просматривать списки и
создавать новые сущности.

Авторизация и аутентификация

Для начала создадим сущность пользователя.

<?php // src/Entity/User.php namespace App\Entity;  use Sonata\UserBundle\Entity\BaseUser as BaseUser; use Doctrine\ORM\Mapping as ORM;  /**  * @ORM\Entity  * @ORM\Table(name="fos_user")  */ class User extends BaseUser {     /**      * @ORM\Id      * @ORM\Column(type="integer", options={"unsigned":true})      * @ORM\GeneratedValue(strategy="AUTO")      */     protected $id;      /**      * @return int      */     public function getId()     {         return $this->id;     } }

И заодно создадим сущность группы пользователя.

<?php // src/Entity/Group.php namespace App\Entity;  use Sonata\UserBundle\Entity\BaseGroup as BaseGroup; use Doctrine\ORM\Mapping as ORM;  /**  * @ORM\Entity  * @ORM\Table(name="fos_group")  */ class Group extends BaseGroup {     /**      * @ORM\Id      * @ORM\Column(type="integer", options={"unsigned":true})      * @ORM\GeneratedValue(strategy="AUTO")      */     protected $id;      /**      * @return int      */     public function getId()     {         return $this->id;     } }

После настроим Twig как движок шаблонов.

framework:     ...     templating:         engines: ['twig']

На данный момент есть проблемы с работой Symfony Flex и FOSUserBundle.
Более подробно можно узнать по ссылкам #2562, #2708 и #2801.

Пока данные проблемы не решены, нужно сделать пару дополнительных манипуляций
перед установкой Sonata User Bundle.

# config/service.yaml services:     ...     mailer:         alias: fos_user.mailer.noop         public: true

# config/packages/fos_user.yaml fos_user:   db_driver: orm   firewall_name: main   user_class: App\Entity\User   registration:     confirmation:       enabled: false   from_email:     address: '%env(MAILER_USER_ADDRESS)%'     sender_name: '%env(MAILER_USER_NAME)%'   service:     user_manager: sonata.user.orm.user_manager     mailer: 'fos_user.mailer.noop'   group:     group_class:   App\Entity\Group     group_manager: sonata.user.orm.group_manager

После чего, можно устанавливать бандл.

$ composer require sonata-project/user-bundle

Настройка ACL

В Symfony 4 ACL вынесли в отдельный бандл symfony/acl-bundle. Поэтому его нужно отдельно установить.

composer require symfony/acl-bundle

# config/packages/sonata_user.yaml sonata_user:     security_acl: true     manager_type: orm

# config/packages/acl.yaml acl:     connection: default

Настройка Doctrine

# config/packages/doctrine.yaml doctrine:     orm:         entity_managers:             default:                 mappings:                     SonataUserBundle: ~                     FOSUserBundle: ~

Настройка работы с почтой

Так как в рамках этой статьи работа с почтой не рассматриватеся, то
укажем заглушку вместо реального сервиса.

# config/packages/sonata_user.yaml  sonata_user:     mailer: fos_user.mailer.noop

Интеграция User Bundle в Sonata Admin

# config/routes.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

# config/packages/security.yaml security:     encoders:         FOS\UserBundle\Model\UserInterface: sha512     acl:         connection: default     providers:         fos_userbundle:             id: fos_user.user_provider.username     role_hierarchy:         ROLE_ADMIN:       [ROLE_USER, ROLE_SONATA_ADMIN]         ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]     firewalls:         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     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] }         - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY } 

# config/packages/sonata_user.yaml sonata_user:   ...   class:     user: App\Entity\User     group: App\Entity\Group

Теперь можно обновить ACL и БД.

$ bin/console init:acl

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

Создаем супер пользователя. Указываем имя demo и пароль demo.

$ bin/console fos:user:create --super-admin Please choose a username:demo Please choose an email:demo@demo.com Please choose a password: Created user demo

Для корректной настройки наших Admin классов вместе с ACL нужно внести изменения в настройки.

sonata_admin:     ...     security:          handler: sonata.admin.security.handler.acl

После запустить следующее:

$ bin/console sonata:admin:setup-acl Starting ACL AdminBundle configuration  > install ACL for App\Admin\AddressAdmin    - add role: ROLE_APP\ADMIN\ADDRESSADMIN_GUEST, permissions: ["LIST"]    - add role: ROLE_APP\ADMIN\ADDRESSADMIN_STAFF, permissions: ["LIST","CREATE"]    - add role: ROLE_APP\ADMIN\ADDRESSADMIN_EDITOR, permissions: ["OPERATOR","EXPORT"]    - add role: ROLE_APP\ADMIN\ADDRESSADMIN_ADMIN, permissions: ["MASTER"]  > install ACL for App\Admin\CityAdmin    - add role: ROLE_APP\ADMIN\CITYADMIN_GUEST, permissions: ["LIST"]    - add role: ROLE_APP\ADMIN\CITYADMIN_STAFF, permissions: ["LIST","CREATE"]    - add role: ROLE_APP\ADMIN\CITYADMIN_EDITOR, permissions: ["OPERATOR","EXPORT"]    - add role: ROLE_APP\ADMIN\CITYADMIN_ADMIN, permissions: ["MASTER"]  > install ACL for sonata.user.admin.user    - add role: ROLE_SONATA_USER_ADMIN_USER_GUEST, permissions: ["LIST"]    - add role: ROLE_SONATA_USER_ADMIN_USER_STAFF, permissions: ["LIST","CREATE"]    - add role: ROLE_SONATA_USER_ADMIN_USER_EDITOR, permissions: ["OPERATOR","EXPORT"]    - add role: ROLE_SONATA_USER_ADMIN_USER_ADMIN, permissions: ["MASTER"]  > install ACL for sonata.user.admin.group    - add role: ROLE_SONATA_USER_ADMIN_GROUP_GUEST, permissions: ["LIST"]    - add role: ROLE_SONATA_USER_ADMIN_GROUP_STAFF, permissions: ["LIST","CREATE"]    - add role: ROLE_SONATA_USER_ADMIN_GROUP_EDITOR, permissions: ["OPERATOR","EXPORT"]    - add role: ROLE_SONATA_USER_ADMIN_GROUP_ADMIN, permissions: ["MASTER"]

Теперь при попытке зайти по адресу http://127.0.0.1:8000/admin нас перенаправит
на http://127.0.0.1:8000/admin/login.

Вводим demo/demo и попадаем в административную часть.

Итоги

После всех манипуляций мы получили работающую административную часть Sonata Admin
на Symfony 4 вместе с аутентификацией и авторизацией c помощью Sonata User + ACL.

Всем спасибо. Если будут вопросы и замечания, то я выслушаю их в комментариях.


ссылка на оригинал статьи https://habr.com/ru/post/460345/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *