«Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте.»

Стив Макконнелл

Моё резюме:


June 4, 2014

Установка и настройка SonataAdminBundle + SonataUserBundle + FOSUserBundle

Symfony2, PHP, Sonata Admin

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

Несколько дней назад перед мной снова стала эта задача, я не стал копировать готовые конфиги из предыдущих проектов, а решил поднять эту связку заново на свеже вышедшей версии Symfony 2.5.0.

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

Все действия выполнялись на голом, только что установленном, Symfony 2.5.0.

  1. Для начала, необходимо прописать вот такие зависимости в файл composer.json:

    {
        ....... "require": {
        ......
            "friendsofsymfony/user-bundle": "~1.3", "sonata-project/core-bundle": "dev-master",
            "sonata-project/admin-bundle": "dev-master",
            "sonata-project/doctrine-orm-admin-bundle": "dev-master",
            "sonata-project/easy-extends-bundle": "dev-master",
            "sonata-project/user-bundle": "dev-master",
            "sonata-project/datagrid-bundle": "dev-master"
         }
    }
    Тут есть два важных момента актуальных на момент написания этой статьи. Во-первых, SonataUserBundle работает с FOSUserBundle версии 1.3, возможно скоро они перейдут на использование ветки 2.0, но пока приходится в зависимости указать предыдущую ветку. Во-вторых, надо обязательно включить "sonata-project/core-bundle", в документации про это ничего не написано, но именно этот момент позже дает ошибку.

    Кроме того, эта установка подразумевает использование Doctrine ORM, если же используется MongoDB, то вместо "sonata-project/doctrine-orm-admin-bundle" надо установить "sonata-project/doctrine-mongodb-admin-bundle".

    После этого из папки проекта выполняем команду:

    php composer.phar update
    Или просто "composer update" если composer установлен глобально в системе.

  2. Активируем установленные бандлы, плюс бандлы, которые подтянулись как зависимости, обязательно надо активировать все следующие бандлы:

    <?php
    // app/AppKernel.php

    public function registerBundles()
    {
      $bundles = array(
      // ...
      new Sonata\CoreBundle\SonataCoreBundle(),
      new Sonata\BlockBundle\SonataBlockBundle(),
      new Knp\Bundle\MenuBundle\KnpMenuBundle(),   
      new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
      new Sonata\AdminBundle\SonataAdminBundle(),
      new Sonata\EasyExtendsBundle\SonataEasyExtendsBundle(),   
      new FOS\UserBundle\FOSUserBundle(),
      new Sonata\UserBundle\SonataUserBundle('FOSUserBundle'),
      );
    }

  3. Теперь пропишем конфиги для бандлов в app/config/config.yml:

    # app/config/config.yml

    # активируем поддержку многоязычности
    framework:
      translator: ~

    sonata_block:
      default_contexts: [cms]
      blocks:
      # Enable the SonataAdminBundle block
      sonata.admin.block.admin_list:
      contexts:  [admin]
      sonata.user.block.menu:  # used to display the menu in profile pages
      sonata.user.block.account: # used to display menu option (login option)
      sonata.block.service.text: # used to if you plan to use Sonata user routes


    sonata_user:
      #security_acl: true # Uncomment for ACL support
      manager_type: orm # can be orm or mongodb
       

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

      group:
      group_class:  Sonata\UserBundle\Entity\BaseGroup
      group_manager: sonata.user.orm.group_manager 

      service:
      user_manager: sonata.user.orm.user_manager 

    Далее нужно настроить Doctrine. Тут есть два варианта, если все ваши сущности будут мапиться автоматически, то прописываем следующий конфиг:

    # app/config/config.yml
    
    doctrine:
      dbal:
      types:
      json: Sonata\Doctrine\Types\JsonType
      orm:
      auto_generate_proxy_classes: "%kernel.debug%"
      entity_managers:
      default:
      auto_mapping: true

    Если Вы хотите сами решать что будет мапиться, то следующий конфиг:

    # app/config/config.yml

    doctrine:
      dbal:
      types:
      json: Sonata\Doctrine\Types\JsonType
      orm:
      auto_generate_proxy_classes: "%kernel.debug%"
        entity_managers:
        default:
        mappings:
        #ApplicationSonataUserBundle: ~
        SonataUserBundle: ~
        FOSUserBundle: ~

    Вот в этом случае есть еще одна загвоздка, в документации строчка ApplicationSonataUserBundle: ~ раскомментирована, а т.к. у нас еще пока нет такого бандла, то дальше будет возникать ошибка, поэтому пока что мы её закомментируем.

  4. Настроим security component в файле app/config/security.yml:

    #app/config/security.yml

    security:
      # Uncomment for ACL support
      #acl:
      #  connection: default
       
      role_hierarchy:
      ROLE_ADMIN:  [ROLE_USER, ROLE_SONATA_ADMIN]
      ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
      SONATA:
      - ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT  # comment it when using acl
       
      providers:
      fos_userbundle:
      id: fos_user.user_provider.username

      encoders:
      FOS\UserBundle\Model\UserInterface: sha512

      firewalls:
      # -> 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
      anonymous:  true
      # -> end custom configuration
       
      main:
      pattern: ^/
      form_login:
      provider: fos_userbundle
      csrf_provider: form.csrf_provider
      logout:  true
      anonymous:  true
       
      access_control:
      # URL of FOSUserBundle which need to be available to anonymous users
      - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
      - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
      - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }

      # Admin login page needs to be access without credential
      - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
      - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
      - { path: ^/admin/login_check$, 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 }

  5. Добавляем рутинги бандлов app/config/routing.yml:

    #app/config/routing.yml

    # FOSUserBundle's routing   
    fos_user_security:
      resource: "@FOSUserBundle/Resources/config/routing/security.xml"

    fos_user_profile:
      resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
      prefix: /profile

    fos_user_register:
      resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
      prefix: /register

    fos_user_resetting:
      resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
      prefix: /resetting

    fos_user_change_password:
      resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
      prefix: /profile
       
    # Admin's routing   
    sonata_user:
      resource: '@SonataUserBundle/Resources/config/routing/admin_security.xml'
      prefix: /admin
       
    admin:
      resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
      prefix: /admin

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

  6. Теперь надо создать бандл, который будет расширять функциональность юзера, выполним следующую команду:

    php app/console sonata:easy-extends:generate SonataUserBundle -d src
    Эта команда создаст бандл в пространстве имен Application, внутри будут уже сгенерированы сущности для пользователя и группы, именно в них можно добавлять новые поля, прописывать связи и т.д.

    Далее активируем новый бандл в app/AppKernel.php:

    // app/AppKernel.php

    class AppKernel {
      public function registerbundles()
      {
      return array(
      // ...
      new Application\Sonata\UserBundle\ApplicationSonataUserBundle(),
      )
      }
    }

    И меняем конфиг FOSUserBundle на новые классы сущностей:

    # app/config/config.yml

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

      group:
      group_class:  Application\Sonata\UserBundle\Entity\Group
      group_manager: sonata.user.orm.group_manager 

      service:
      user_manager: sonata.user.orm.user_manager

    Кроме того, самое время раскомментировать строчку в блоке доктрины, про которую я писал выше:

    # app/config/config.yml

    doctrine:
      dbal:
      types:
      json: Sonata\Doctrine\Types\JsonType
      orm:
      auto_generate_proxy_classes: "%kernel.debug%"
      entity_managers:
      default:
      mappings:
      ApplicationSonataUserBundle: ~
      SonataUserBundle: ~
      FOSUserBundle: ~

  7. Устанавливаем ресурсы бандлов, чистим кеш и обновляем схему БД:

    php app/console assets:install web
    php app/console cache:clear
    php app/console doctrine:schema:update --force
  8. Всё, админка уже работает и доступна по адресу http://yoursite.dev/app_dev.php/admin


    Если система голая и пользователей нет, то можно добавить админа командой:

    fos:user:create [--super-admin] [--inactive] username email password
    php app/console fos:user:create --super-admin admin admin@yoursite.dev qwerty
    После этого входим в админку:


    Можно управлять пользователями и группами:


  9. А что, если нам нужно управлять какой-нибудь другой сущностью? Например, у нас есть блог и для этого блога нужно управлять постами, предположим, что сущность поста у нас уже есть. Во-первых, нам надо создать админ класс, в котором укажем какие поля мы хотим видеть для различных действий (список, поля формы, фильтр):

    <?php
    // src/Acme/BlogBundle/Admin/PostAdmin.php

    namespace SW\BlogBundle\Admin;
     
    use Sonata\AdminBundle\Admin\Admin;
    use Sonata\AdminBundle\Form\FormMapper;
    use Sonata\AdminBundle\Datagrid\DatagridMapper;
    use Sonata\AdminBundle\Datagrid\ListMapper;
    use Sonata\AdminBundle\Show\ShowMapper;
     
    class PostAdmin extends Admin
    {
      /**
      * Конфигурация формы редактирования записи
      *
      * @param \Sonata\AdminBundle\Form\FormMapper $formMapper
      */
      protected function configureFormFields(FormMapper $formMapper)
      {
      $formMapper
      ->add('title', null, array('label' => 'Заголовок', 'attr' => array('style' => 'width:583px')))
      ->add('short_text', null, array('label' => 'Короткий текст', 'attr' => array('class' => 'foreditor', 'style' => 'width:569px;height:100px;')))
      ->add('text', null, array('label' => 'Текст', 'attr' => array('class' => 'foreditor', 'style' => 'width:569px;height:200px;')))
      ->add('tags', 'sonata_type_model', array('label' => 'Теги', 'by_reference' => false, 'multiple' => true, 'expanded' => true));
      }
     
      /**
      * Конфигурация списка записей
      *
      * @param \Sonata\AdminBundle\Datagrid\ListMapper $listMapper
      */
      protected function configureListFields(ListMapper $listMapper)
      {
      $listMapper
      ->addIdentifier('id')
      ->addIdentifier('title', null, array('label' => 'Заголовок'))
      ->add('slug', null, array('label' => 'URL'));
      }
     
      /**
      * Поля, по которым производится поиск в списке записей
      *
      * @param \Sonata\AdminBundle\Datagrid\DatagridMapper $datagridMapper
      */
      protected function configureDatagridFilters(DatagridMapper $datagridMapper)
      {
      $datagridMapper
      ->add('title', null, array('label' => 'Заголовок'));
      }   
    }

    Далее этот класс надо зарегистрировать как сервис, для этого создадим файл src/Acme/BlogBundle/Resources/config/admin.xml:

    <?xml version="1.0" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
      <services>
      <service id="acme.admin.post" class="Acme\BlogBundle\Admin\PostAdmin">
      <tag name="sonata.admin" manager_type="orm" group="Посты" label="Посты"/>
      <argument/>
      <argument>Acme\BlogBundle\Entity\Blog\Post</argument>
      <argument>SonataAdminBundle:CRUD</argument>
      </service>  
      </services>
    </container>

    Осталось подключить этот файл в общем конфиге:

    # app/config/config.yml

    imports:
      - { resource: parameters.yml }
      - { resource: security.yml }
      - { resource: @AcmeBlogBundle/Resources/config/admin.xml }

    На этом все манипуляции завершены, теперь можно управлять и постами в админке.

    Надеюсь кому-то эта инструкция будет полезной и сэкономит время.


comments powered by Disqus