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

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

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

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

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

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

$ composer create-project symfony/skeleton sonatademo

$ cd sonatademo

Для работы с встроенным веб сервером Symfony нужно установить Symfony Client.

Запускаем.

$ 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: ['en'] }

Выполняем

$ composer dump-env dev

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

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

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

<?php
// src/Entity/City.php
namespace AppEntity;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMMapping as ORM;
/**
 * @ORMEntity
 */
class City
{
    /**
     * @ORMId()
     * @ORMGeneratedValue()
     * @ORMColumn(type="integer", options={"unsigned":true})
     */
    private $id;
    /**
     * @var string
     *
     * @ORMColumn(type="string")
     */
    private $title;
    /**
     * @var string
     *
     * @ORMColumn(type="text")
     */
    private $description;
    /**
     * @var bool
     *
     * @ORMColumn(type="boolean")
     */
    private $isBig = false;
    /**
     * @ORMOneToMany(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 AppEntity;
use DoctrineORMMapping as ORM;
/**
 * @ORMEntity
 */
class Address
{
    /**
     * @ORMId()
     * @ORMGeneratedValue()
     * @ORMColumn(type="integer", options={"unsigned":true})
     */
    private $id;
    /**
     * @var string
     *
     * @ORMColumn(type="string")
     */
    private $title;
    /**
     * @var string
     *
     * @ORMColumn(type="text")
     */
    private $description;
    /**
     * @ORMManyToOne(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 AppAdmin;
use AppEntityAddress;
use AppEntityCity;
use SonataAdminBundleAdminAbstractAdmin;
use SonataAdminBundleDatagridListMapper;
use SonataAdminBundleDatagridDatagridMapper;
use SonataAdminBundleFormFormMapper;
use SonataAdminBundleFormTypeCollectionType;
use SonataAdminBundleFormTypeModelType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormExtensionCoreTypeCheckboxType;
use SymfonyComponentFormExtensionCoreTypeTextareaType;
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 AppAdmin;
use AppEntityCity;
use SonataAdminBundleAdminAbstractAdmin;
use SonataAdminBundleDatagridListMapper;
use SonataAdminBundleDatagridDatagridMapper;
use SonataAdminBundleFormFormMapper;
use SonataAdminBundleFormTypeModelType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormExtensionCoreTypeCheckboxType;
use SymfonyComponentFormExtensionCoreTypeTextareaType;
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:
    ...
    AppAdminCityAdmin:
        arguments: [~, AppEntityCity, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: City }
    AppAdminAddressAdmin:
        arguments: [~, AppEntityAddress, ~]
        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 AppEntity;
use SonataUserBundleEntityBaseUser as BaseUser;
use DoctrineORMMapping as ORM;
/**
 * @ORMEntity
 * @ORMTable(name="fos_user")
 */
class User extends BaseUser
{
    /**
     * @ORMId
     * @ORMColumn(type="integer", options={"unsigned":true})
     * @ORMGeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }
}

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

<?php
// src/Entity/Group.php
namespace AppEntity;
use SonataUserBundleEntityBaseGroup as BaseGroup;
use DoctrineORMMapping as ORM;
/**
 * @ORMEntity
 * @ORMTable(name="fos_group")
 */
class Group extends BaseGroup
{
    /**
     * @ORMId
     * @ORMColumn(type="integer", options={"unsigned":true})
     * @ORMGeneratedValue(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: AppEntityUser
  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:   AppEntityGroup
    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:
        FOSUserBundleModelUserInterface: 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:  ^/(_(profilerwdt)cssimagesjs)/
            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: AppEntityUser
    group: AppEntityGroup

Теперь можно обновить 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 AppAdminAddressAdmin
   - add role: ROLE_APPADMINADDRESSADMIN_GUEST, permissions: ["LIST"]
   - add role: ROLE_APPADMINADDRESSADMIN_STAFF, permissions: ["LIST","CREATE"]
   - add role: ROLE_APPADMINADDRESSADMIN_EDITOR, permissions: ["OPERATOR","EXPORT"]
   - add role: ROLE_APPADMINADDRESSADMIN_ADMIN, permissions: ["MASTER"]
 > install ACL for AppAdminCityAdmin
   - add role: ROLE_APPADMINCITYADMIN_GUEST, permissions: ["LIST"]
   - add role: ROLE_APPADMINCITYADMIN_STAFF, permissions: ["LIST","CREATE"]
   - add role: ROLE_APPADMINCITYADMIN_EDITOR, permissions: ["OPERATOR","EXPORT"]
   - add role: ROLE_APPADMINCITYADMIN_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.

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

Теги:

Похожие публикации

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.