Greetings to all. This article will talk about symfony 4 and Sonata Admin.
During the installation, I encountered a lot of inaccuracies in the documentation and the documentation itself was scattered in several places. Here I will review the whole process, starting from the creation of a project and ending with authorization along with authentication.
Separate parts of the settings are taken from the official documentation, part was taken from the comments on GitHub, where installation problems were discussed. Also described possible pitfalls and ways to circumvent them.
$ composer create-project symfony/skeleton sonatademo
$ cd sonatademo
To work with the symfony web server, you need to install the symfony client
We start.
$ symfony serve
Go to the link http://127.0.0.1:8000/ and get a standard symfony greeting. So everything works correctly.
$ composer require sonata-project/admin-bundle
In order to interact with the database, you need to install one of the three libraries:
In this article, I use SonataDoctrineORMAdminBundle
, which is more than enough to work with MySQL
or Sqlite
.
Install the SonataDoctrineORMAdminBundle
.
$ composer require sonata-project/doctrine-orm-admin-bundle
Now we will configure work with Sqlite
.
Open the .env file and in the ###> doctrine/doctrine-bundle ###
section change the DATABASE_URL
.
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
Since Symfony Flex does almost all the work for us during installation, it remains to do a few manipulations to get the final result.
# config/packages/framework.yaml framework: translator: { fallbacks: ['en'] }
We carry out
$ composer dump-env dev
Now go to the link http://127.0.0.1:8000/admin and see the empty standard administrative interface.
Let's create two entities connected by a one-to-many connection.
<?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; } }
Synchronize with the database.
bin/console doctrine:schema:create
It is necessary for each entity to create a separate file with a description of how Sonata Admin should work.
<?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'); } }
These classes need to be described in 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 }
If errors occur when entering the administrative part, for example, Unable to generate a URL for the named route
, then you need to clean the application cache and try again.
bin/console cache:clear
Now, if you follow the link http://127.0.0.1:8000/admin you will see a list of two elements Address
and City
, where you can view the lists and create new entities.
First, create the user entity.
<?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; } }
And at the same time create the user group entity.
<?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; } }
After you configure Twig as a template engine.
framework: ... templating: engines: ['twig']
At the moment there are problems with the work of Symfony Flex and FOSUserBundle. For more information, see links # 2562 , # 2708 and # 2801 .
While these problems are not solved, you need to do a couple of additional manipulations before installing the 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
After that, you can install a bundle.
$ composer require sonata-project/user-bundle
In symfony 4, the ACL was brought to a separate symfony/acl-bundle
. Therefore, it must be separately installed.
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
# config/packages/doctrine.yaml doctrine: orm: entity_managers: default: mappings: SonataUserBundle: ~ FOSUserBundle: ~
Since, within the framework of this article, work with mail is not considered, then we will indicate a stub instead of a real service.
# config/packages/sonata_user.yaml sonata_user: mailer: fos_user.mailer.noop
# 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
Now you can update the ACL and DB.
$ bin/console init:acl
$ php bin/console doctrine:schema:update --force
Create a super user. Specify the name demo
and password 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
To correctly configure our Admin classes along with the ACL, you need to make changes to the settings.
sonata_admin: ... security: handler: sonata.admin.security.handler.acl
After running the following:
$ 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"]
Now, when you try to go to http://127.0.0.1:8000/admin, we will be redirected to http://127.0.0.1:8000/admin/login .
Enter the demo / demo and get into the administrative part.
After all the manipulations, we got a working administrative part of Sonata Admin on Symfony 4 along with authentication and authorization using Sonata User + ACL.
Thanks to all. If you have questions and comments, I will listen to them in the comments.
Source: https://habr.com/ru/post/460345/
All Articles