Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building great admin panels with Symfony and SonataAdminBundle

Building great admin panels with Symfony and SonataAdminBundle

Coding an admin panel is not the problem you love to face when you wake up in the morning, but is something you need to do very often. Having an environment that allows you to implement this efficiently, is something key for a developer. Symfony and SonataAdminBundle provide a way to do it that gives you as many out-of-the-box functionalities as you could dream of, plus the chance to customize every piece -if you know how to do it. The purpose of this talk is to show how easily can you get profit of SonataAdminBundle once you know how to deal with it.

We will start with a clean installation of Symfony and SonataAdminBundle, showing how to build up form there, and how to solve the main problems and questions that you usually face when having a complex admin panel in your hands. The main purposes are to show how to use SonataAdminBundle in the most standard way, and how to build up from that point, customizing whatever you need to meet the exact requirements of the project.

Victoria Quirante

February 04, 2017
Tweet

More Decks by Victoria Quirante

Other Decks in Programming

Transcript

  1. I work at Limenius We build tailor-made projects with Symfony

    and React Almost every project needs an admin panel We rely on SonataAdminBundle to deal with this part Victoria Quirante @vicqr [email protected]
  2. Some context “A bundle is simply a structured set of

    files within a directory that implement a single feature.“
  3. Some context Released with no ‘official’ admin panel solution “A

    bundle is simply a structured set of files within a directory that implement a single feature.“
  4. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation - A lot of code, difficult to dig in
  5. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation - A lot of code, difficult to dig in
  6. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation - A lot of code, difficult to dig in
  7. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation can still improve - A lot of code, difficult to dig in
  8. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation can still improve - A lot of code, difficult to dig in
  9. Main problems historically attributed - Difficult to install - Ugly

    - Poor documentation can still improve - A lot of code, difficult to dig in But that’s why we are here
  10. What Sonata is, what it is not It is not

    Michelangelo’s David But it is something very useful
  11. What Sonata is, what it is not It is not

    Michelangelo’s David But it is something very useful Like Symfony, like PHP
  12. Practical talk Not an exhaustive review of the documentation A

    walk-through from a clean installation showing:
  13. Practical talk Not an exhaustive review of the documentation A

    walk-through from a clean installation showing: 1. What Sonata gives you “for free”
  14. Practical talk Not an exhaustive review of the documentation A

    walk-through from a clean installation showing: 1. What Sonata gives you “for free” 2. How you customize everything from that point
  15. Install Symfony • Get the Symfony Installer • symfony new

    my_project_name • php bin/console server:run
  16. Install Symfony • Get the Symfony Installer • symfony new

    my_project_name • php bin/console server:run http://symfony.com/doc/current/setup.html Symfony documentation is very good
  17. Install SonataAdminBundle • composer require sonata-project/admin-bundle • composer require sonata-project/doctrine-orm-admin-bundle

    • Activate bundles • Add configuration to config.yml and routing.yml https://sonata-project.org/bundles/admin/master/doc/reference/installation.html
  18. We need first to have an entity We create our

    first entity with Symfony (Doctrine)
  19. We need first to have an entity We create our

    first entity with Symfony (Doctrine) php bin/console generate:doctrine:entity
  20. We need first to have an entity /** * @ORM\Table(name="gift")

    * @ORM\Entity(repositoryClass="AppBundle\Repository\GiftRepository") */ class Gift { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="name", type="string", length=255) */ private $name; }
  21. We need first to have an entity /** * @ORM\Table(name="gift")

    * @ORM\Entity(repositoryClass="AppBundle\Repository\GiftRepository") */ class Gift { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="name", type="string", length=255) */ private $name; }
  22. We need first to have an entity /** * @ORM\Table(name="gift")

    * @ORM\Entity(repositoryClass="AppBundle\Repository\GiftRepository") */ class Gift { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="name", type="string", length=255) */ private $name; }
  23. We need first to have an entity /** * @ORM\Table(name="gift")

    * @ORM\Entity(repositoryClass="AppBundle\Repository\GiftRepository") */ class Gift { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="name", type="string", length=255) */ private $name; } And now we can start working with SonataAdminBundle
  24. Basic Admin: 2 steps, 2 minutes Have entity Gift, want

    to create Admin: 1) Create GiftAdmin class 2) Configure the service
  25. Basic Admin: 2 steps, 2 minutes Have entity Gift, want

    to create Admin: 1) Create GiftAdmin class 2) Configure the service You may want to read about Symfony’s Service Container http://symfony.com/doc/current/service_container.html
  26. Basic Admin: 2 steps, 2 minutes Have entity Gift, want

    to create Admin: 1) Create GiftAdmin class 2) Configure the service You may want to read about Symfony’s Service Container http://symfony.com/doc/current/service_container.html
  27. Basic Admin - Step 1: Create Admin class # src/AppBundle/Admin/GiftAdmin.php

    class GiftAdmin extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper->add('name'); } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper->add('name'); } protected function configureListFields(ListMapper $listMapper) { $listMapper->addIdentifier('name'); } }
  28. Basic Admin - Step 1: Create Admin class # src/AppBundle/Admin/GiftAdmin.php

    class GiftAdmin extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper->add('name'); } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper->add('name'); } protected function configureListFields(ListMapper $listMapper) { $listMapper->addIdentifier('name'); } }
  29. Basic Admin - Step 1: Create Admin class # src/AppBundle/Admin/GiftAdmin.php

    class GiftAdmin extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper->add('name'); } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper->add('name'); } protected function configureListFields(ListMapper $listMapper) { $listMapper->addIdentifier('name'); } }
  30. Basic Admin - Step 2: Register service # app/config/services.yml services:

    admin.gift class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, ~] tags: - { name: sonata.admin, manager_type: orm, label: Gifts }
  31. Basic Admin - Step 2: Register service # app/config/services.yml services:

    admin.gift: class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, ~] tags: - { name: sonata.admin, manager_type: orm, label: Gifts }
  32. Basic Admin - Step 3 (optional): Sidebar # app/config/config.yml sonata_admin:

    dashboard: groups: demo.admin.main: label: Admin label_catalogue: AppBundle icon: '<i class="fa fa-database"></i>' items: - admin.gift
  33. Basic Admin - Step 3 (optional): Sidebar # app/config/config.yml sonata_admin:

    dashboard: groups: demo.admin.main: label: Admin label_catalogue: AppBundle icon: '<i class="fa fa-database"></i>' items: - admin.gift
  34. Basic Admin - Step 3 (optional): Sidebar # app/config/config.yml sonata_admin:

    dashboard: groups: demo.admin.main: label: Admin label_catalogue: AppBundle icon: '<i class="fa fa-database"></i>' items: - admin.gift
  35. Basic Admin - What do I get? Create, edit, delete…

    Sortable, exportable, filterable, paginated list
  36. Basic Admin - What do I get? Create, edit, delete…

    Sortable, exportable, filterable, paginated list
  37. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->add('name') ; } }
  38. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->add('name') ->add('price') ->add('description') ->add('addressee') ->add('buyer') ; } }
  39. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('price') ->add('description') ->add('addressee') ->add('buyer') ; } }
  40. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('price', 'currency', array('currency' => 'USD')) ->add('description') ->add('addressee') ->add('buyer') ; } }
  41. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('price', 'currency', array('currency' => 'USD')) ->add('description', null, array('label' => 'Details')) ->add('addressee') ->add('buyer') ; } }
  42. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('price', 'currency', array('currency' => 'USD')) ->add('description', null, array('label' => 'Details')) ->add('addressee', null, array('editable' => true)) ->add('buyer') ; } }
  43. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('price', 'currency', array('currency' => 'USD')) ->add('description', null, array('label' => 'Details')) ->add('addressee', null, array('editable' => true)) ->add('buyer') ; } } https://sonata-project.org/bundles/admin/master/doc/reference/field_types.html
  44. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ... ->add('_action', null, array( 'actions' => array( 'show' => array(), 'edit' => array(), 'delete' => array(), ) )) ; } }
  45. Basic list configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureListFields(ListMapper $listMapper) { $listMapper ... ->add('_action', null, array( 'actions' => array( 'show' => array(), 'edit' => array(), 'delete' => array(), ) )) ; } }
  46. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name') ; } }
  47. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name') ->add('price') ->add('description') ->add('addressee') ->add('buyer') ; } }
  48. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name') ->add('price') ->add('description') ->add('addressee') ->add('buyer') ; } }
  49. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('Gift', array('class' => 'col-md-6')) ->add('name') ->add('price') ->add('description') ->end() ->with('Participants', array('class' => 'col-md-6')) ->add('addressee') ->add('buyer') ->end() ; } }
  50. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin {

    protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('Gift', array('class' => 'col-md-6')) ->add('name') ->add('price') ->add('description') ->end() ->with('Participants', array('class' => 'col-md-6')) ->add('addressee') ->add('buyer') ->end() ; } }
  51. Basic form configuration # src/AppBundle/Admin/GiftAdmin.php protected function configureFormFields(FormMapper $formMapper) {

    $formMapper ->tab('Tab 1') ->with('Gift', array('class' => 'col-md-6')) ->add('name') ->add('price') ->add('description') ->end() ->with('Participants', array('class' => 'col-md-6')) ->add('addressee') ->add('buyer') ->end() ->end() ->tab('Tab 2') ->end() ; }
  52. Many-to-one relations # src/AppBundle/Entity/Gift.php class Gift { … /** *

    @Column(type="string") */ private $addressee; … }
  53. Many-to-one relations # src/AppBundle/Entity/Gift.php class Gift { … /** *

    @ORM\ManyToOne(targetEntity="Addressee") */ private $addressee; … }
  54. Many-to-one relations # src/AppBundle/Entity/Gift.php class Gift { … /** *

    @ORM\ManyToOne(targetEntity="Addressee") */ private $addressee; … } We don’t need to do anything in our Admin class
  55. Many-to-one relations # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin { protected

    function configureFormFields(FormMapper $formMapper) { $formMapper ... ->with('Participants', array('class' => 'col-md-6')) ->add('addressee') ->add('buyer') ->end() ; } }
  56. Many-to-one relations # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin { protected

    function configureFormFields(FormMapper $formMapper) { $formMapper ... ->with('Participants', array('class' => 'col-md-6')) ->add('addressee', null) ->add('buyer', null) ->end() ; } }
  57. Many-to-one relations # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin { protected

    function configureFormFields(FormMapper $formMapper) { $formMapper ... ->with('Participants', array('class' => 'col-md-6')) ->add('addressee', 'entity', array( 'class' => 'AppBundle\Entity\Addressee')) ->add('buyer', 'entity', array( 'class' => 'AppBundle\Entity\Buyer')) ->end() ; } }
  58. Many-to-one relations # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin { protected

    function configureFormFields(FormMapper $formMapper) { $formMapper ... ->with('Participants', array('class' => 'col-md-6')) ->add('addressee', null, array( 'class' => 'AppBundle\Entity\Addressee', 'choice_label' => 'lastName')) ->add('buyer', null, array( 'class' => 'AppBundle\Entity\Buyer', 'choice_label' => 'lastName')) ->end() ; } }
  59. Many-to-one relations # src/AppBundle/Admin/GiftAdmin.php class GiftAdmin extends AbstractAdmin { protected

    function configureFormFields(FormMapper $formMapper) { $formMapper ... ->with('Participants', array('class' => 'col-md-6')) ->add('addressee', null, array( 'class' => 'AppBundle\Entity\Addressee', 'choice_label' => 'fullName')) ->add('buyer', null, array( 'class' => 'AppBundle\Entity\Buyer', 'choice_label' => 'fullName')) ->end() ; } }
  60. A collection in the form (one-to-many) # src/AppBundle/Admin/BuyerAdmin.php class BuyerAdmin

    extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('Personal Data', array('class' => 'col-md-6')) ->add('firstName') ->add('lastName') ->end() ; } }
  61. A collection in the form (one-to-many) # src/AppBundle/Admin/BuyerAdmin.php class BuyerAdmin

    extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('Personal Data', array('class' => 'col-md-6')) ->add('firstName') ->add('lastName') ->end() ->with('Payments', array('class' => 'col-md-6')) ->add('payments', 'sonata_type_collection', array( ), array( 'edit' => 'inline', 'inline' => 'table', )) ->end(); } }
  62. A collection in the form (one-to-many) # src/AppBundle/Admin/BuyerAdmin.php class BuyerAdmin

    extends AbstractAdmin { protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('Personal Data', array('class' => 'col-md-6')) ->add('firstName') ->add('lastName') ->end() ->with('Payments', array('class' => 'col-md-6')) ->add('payments', 'sonata_type_collection', array( ), array( 'edit' => 'inline', 'inline' => 'table', )) ->end(); } }
  63. A many-to-many in the form # src/AppBundle/Admin/GiftAdmin.php protected function configureFormFields(FormMapper

    $formMapper) { $formMapper ... ->with('Shops', array('class' => 'col-md-6')) ->add('shops', 'sonata_type_model', array( 'by_reference' => false, 'expanded' => true, 'multiple' => true, 'label' => 'Shops') ) ->end(); }
  64. A many-to-many in the form # src/AppBundle/Admin/GiftAdmin.php protected function configureFormFields(FormMapper

    $formMapper) { $formMapper ... ->with('Shops', array('class' => 'col-md-6')) ->add('shops', 'sonata_type_model', array( 'by_reference' => false, 'expanded' => true, 'multiple' => true, 'label' => 'Shops') ) ->end(); }
  65. A many-to-many in the form # src/AppBundle/Admin/GiftAdmin.php protected function configureFormFields(FormMapper

    $formMapper) { $formMapper ... ->with('Shops', array('class' => 'col-md-6')) ->add('shops', 'sonata_type_model', array( 'by_reference' => false, 'expanded' => true, 'multiple' => true, 'label' => 'Shops') ) ->end(); }
  66. Basic filters configuration # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper) {

    $datagridMapper ->add('name') ->add('price') ->add('addressee', null, array(), 'entity', array( 'class' => 'AppBundle\Entity\Addressee', 'choice_label' => 'fullName')) ; }
  67. Basic filters configuration # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper) {

    $datagridMapper ->add('name') ->add('price') ->add('addressee', null, array(), 'entity', array( 'class' => 'AppBundle\Entity\Addressee', 'choice_label' => 'fullName')) ; } Careful with the number of arguments, different in configureFormFields
  68. Basic Admin - How much do I get? With very

    little effort you have a lot Actually, you get most of what you need
  69. Basic Admin - How much do I get? With very

    little effort you have a lot Actually, you get most of what you need But the world is not perfect… … and we will need more stuff
  70. And what if I want to… … have a form

    with a different layout? … add something ‘weird’ in a list field? … create a section in the sidebar that is not an Admin? … add something that has nothing to do with the admin panel?
  71. And what if I want to… … have a form

    with a different layout? … add something ‘weird’ in a list field? … create a section in the sidebar that is not an Admin? … add something that has nothing to do with the admin panel? How much effort will that take? Wouldn’t it be easier to start from scratch?
  72. Customizing templates # app/config/config.yml sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig' add_block: 'SonataAdminBundle:Core:add_block.html.twig'

    layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  73. Customizing templates # app/config/config.yml sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig' add_block: 'SonataAdminBundle:Core:add_block.html.twig'

    layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ... https://sonata-project.org/bundles/admin/master/doc/reference/templates.html
  74. Customizing templates # app/config/config.yml sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig' add_block: 'SonataAdminBundle:Core:add_block.html.twig'

    layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  75. Customizing templates # app/config/config.yml sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig' add_block: 'SonataAdminBundle:Core:add_block.html.twig'

    layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'AppBundle:Admin:edit.html.twig' ...
  76. Customizing templates # app/config/config.yml sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig' add_block: 'SonataAdminBundle:Core:add_block.html.twig'

    layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'AppBundle:Admin:edit.html.twig' ... Find the original template and see what exactly you want to override
  77. Customizing templates # vendor/sonata-project/admin-bundle/Resources/views/CRUD/edit.html.twig {% extends 'SonataAdminBundle:CRUD:base_edit.html.twig' %} # vendor/sonata-project/admin-bundle/Resources/views/CRUD/base_edit.html.twig

    {% use 'SonataAdminBundle:CRUD:base_edit_form.html.twig' with form as parentForm %} {% block form %} {{ block('parentForm') }} {% endblock %} http://twig.sensiolabs.org/doc/2.x/tags/use.html
  78. Customizing templates # vendor/sonata-project/admin-bundle/Resources/views/CRUD/edit.html.twig {% extends 'SonataAdminBundle:CRUD:base_edit.html.twig' %} # vendor/sonata-project/admin-bundle/Resources/views/CRUD/base_edit.html.twig

    {% use 'SonataAdminBundle:CRUD:base_edit_form.html.twig' with form as parentForm %} {% block form %} {{ block('parentForm') }} {% endblock %} # vendor/sonata-project/admin-bundle/Resources/views/CRUD/base_edit_form.html.twig {% block formactions %} ... {% block formactions %}
  79. Customizing templates Our custom edit would look like this #

    app/Resources/views/Admin/edit.html.twig {% extends 'SonataAdminBundle:CRUD:edit.html.twig' %} {% block formactions %} ... {% block formactions %}
  80. Customizing templates Our custom edit would look like this #

    app/Resources/views/Admin/edit.html.twig {% extends 'SonataAdminBundle:CRUD:edit.html.twig' %} {% block formactions %} ... {% block formactions %}
  81. Customizing templates Our custom edit would look like this #

    app/Resources/views/Admin/edit.html.twig {% extends 'SonataAdminBundle:CRUD:edit.html.twig' %} {% block formactions %} ... {% block formactions %} Just Twig inheritance, but important when working with Sonata
  82. Customizing template for a single Admin # app/config/services.yml services: admin.buyer

    class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers }
  83. Customizing template for a single Admin # app/config/services.yml services: admin.buyer

    class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers } calls: - [ setTemplate, [edit, :Admin:edit_buyer.html.twig]]
  84. Customizing a single field on the list (step 1) #

    src/AppBundle/Admin/GiftAdmin.php protected function configureListFields(ListMapper $listMapper) { $listMapper ... ; }
  85. Customizing a single field on the list (step 1) #

    src/AppBundle/Admin/GiftAdmin.php protected function configureListFields(ListMapper $listMapper) { $listMapper ... ->add('myField', 'string', array('template' => ':Admin:field_send_email.html.twig')) ; }
  86. Customizing a single field on the list (step 1) #

    src/AppBundle/Admin/GiftAdmin.php protected function configureListFields(ListMapper $listMapper) { $listMapper ... ->add('myField', 'string', array('template' => ':Admin:field_send_email.html.twig')) ; }
  87. Customizing a single field on the list (step 2) #

    app/Resources/views/Admin/field_send_email.html.twig {% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %} {% block field %} <a class="btn btn-primary btn-sm" href=""> <i class="fa fa-envelope"></i> Send </a> {% endblock %}
  88. Customizing the list query # src/AppBundle/Admin/GiftAdmin.php public function createQuery($context =

    'list') { $query = parent::createQuery($context); $rootAlias = $query->getRootAliases()[0]; $query ->andWhere( $query->expr()->eq($rootAlias.'.delivered', ':wasDelivered')); $query->setParameter('wasDelivered', false); return $query; }
  89. Customizing the list query # src/AppBundle/Admin/GiftAdmin.php public function createQuery($context =

    'list') { $query = parent::createQuery($context); $rootAlias = $query->getRootAliases()[0]; $query ->andWhere( $query->expr()->eq($rootAlias.'.delivered', ':wasDelivered')); $query->setParameter('wasDelivered', false); return $query; }
  90. Customizing the list query # src/AppBundle/Admin/GiftAdmin.php public function createQuery($context =

    'list') { $query = parent::createQuery($context); $rootAlias = $query->getRootAliases()[0]; $query ->andWhere( $query->expr()->eq($rootAlias.'.delivered', ':wasDelivered')); $query->setParameter('wasDelivered', false); return $query; } http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
  91. Customizing the filter query # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper)

    { $datagridMapper ->add('myFilter', 'doctrine_orm_callback', array( 'callback' => function($queryBuilder, $alias, $field, $value) { if (!$value['value']) { return; } $queryBuilder->andWhere($alias.'.status != :status'); $queryBuilder->setParameter('status', 'delivered'); return true; }, 'field_type' => 'checkbox', 'label' => 'Not delivered' )); }
  92. Customizing the filter query # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper)

    { $datagridMapper ->add('myFilter', 'doctrine_orm_callback', array( 'callback' => function($queryBuilder, $alias, $field, $value) { if (!$value['value']) { return; } $queryBuilder->andWhere($alias.'.status != :status'); $queryBuilder->setParameter('status', 'delivered'); return true; }, 'field_type' => 'checkbox', 'label' => 'Not delivered' )); }
  93. Customizing the filter query # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper)

    { $datagridMapper ->add('myFilter', 'doctrine_orm_callback', array( 'callback' => function($queryBuilder, $alias, $field, $value) { if (!$value['value']) { return; } $queryBuilder->andWhere($alias.'.status != :status'); $queryBuilder->setParameter('status', 'delivered'); return true; }, 'field_type' => 'checkbox', 'label' => 'Not delivered' )); }
  94. Customizing the filter query # src/AppBundle/Admin/GiftAdmin.php protected function configureDatagridFilters(DatagridMapper $datagridMapper)

    { $datagridMapper ->add('myFilter', 'doctrine_orm_callback', array( 'callback' => function($queryBuilder, $alias, $field, $value) { if (!$value['value']) { return; } $queryBuilder->andWhere($alias.'.status != :status'); $queryBuilder->setParameter('status', 'delivered'); return true; }, 'field_type' => 'checkbox', 'label' => 'Not delivered' )); }
  95. Creating a custom action (step 1) # src/AppBundle/Controller/GiftAdminController.php use Sonata\AdminBundle\Controller\CRUDController

    as Controller; use Symfony\Component\HttpFoundation\RedirectResponse; class GiftAdminController extends Controller { public function sendEmailAction() { $regalo = $this->admin->getSubject(); $email = $regalo->getAddressee()->getEmail(); // Here code to send email $this->addFlash('sonata_flash_success', 'Email sent to '.$email); return new RedirectResponse($this->admin->generateUrl('list')); } }
  96. Creating a custom action (step 1) # src/AppBundle/Controller/GiftAdminController.php use Sonata\AdminBundle\Controller\CRUDController

    as Controller; use Symfony\Component\HttpFoundation\RedirectResponse; class GiftAdminController extends Controller { public function sendEmailAction() { $regalo = $this->admin->getSubject(); $email = $regalo->getAddressee()->getEmail(); // Here code to send email $this->addFlash('sonata_flash_success', 'Email sent to '.$email); return new RedirectResponse($this->admin->generateUrl('list')); } }
  97. Creating a custom action (step 2) # app/config/services.yml services: admin.gift:

    class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, ~] tags: - { name: sonata.admin, manager_type: orm, label: Gifts } calls: - [ setTemplate, [edit, :Admin:edit_gift.html.twig]]
  98. Creating a custom action (step 2) # app/config/services.yml services: admin.gift:

    class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, AppBundle\GiftAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Gifts } calls: - [ setTemplate, [edit, :Admin:edit_gift.html.twig]]
  99. Creating a custom action (step 3) # src/AppBundle/Admin/GiftAdmin.php protected function

    configureRoutes(RouteCollection $collection) { $collection->add('sendEmail', $this->getRouterIdParameter().'/send-email'); }
  100. Creating a custom action # app/Resources/views/Admin/field_send_email.html.twig {% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}

    {% block field %} <a class="btn btn-primary btn-sm" href=""> <i class="fa fa-envelope"></i> Send </a> {% endblock %}
  101. Creating a custom action # app/Resources/views/Admin/field_send_email.html.twig {% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}

    {% block field %} <a class="btn btn-primary btn-sm" href="{{ admin.generateObjectUrl('sendEmail', object) }}"> <i class="fa fa-envelope"></i> Send </a> {% endblock %}
  102. Creating a custom batch action (step 1) # src/AppBundle/Controller/GiftAdminController.php public

    function batchActionSendEmail($selectedModelQuery) { // Here code to send emails return new RedirectResponse($this->admin->generateUrl('list')); }
  103. Creating a custom batch action (step 1) # src/AppBundle/Controller/GiftAdminController.php public

    function batchActionSendEmail($selectedModelQuery) { // Here code to send emails return new RedirectResponse($this->admin->generateUrl('list')); }
  104. Creating a custom batch action (step 2) # src/AppBundle/Admin/GiftAdmin.php public

    function getBatchActions() { $actions = parent::getBatchActions(); if ($this->hasRoute('edit') && $this->isGranted('EDIT')) { $actions['send_email'] = [ 'label' => 'Send email', 'ask_confirmation' => false, ]; } return $actions; }
  105. Creating a custom batch action (step 2) # src/AppBundle/Admin/GiftAdmin.php public

    function getBatchActions() { $actions = parent::getBatchActions(); if ($this->hasRoute('edit') && $this->isGranted('EDIT')) { $actions['send_email'] = [ 'label' => 'Send email', 'ask_confirmation' => false, ]; } return $actions; }
  106. IV. Useful tips Or how to solve a few things

    that we need in every project
  107. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css - bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css - bundles/sonatacore/vendor/select2/select2.css - bundles/sonataadmin/css/styles.css - bundles/sonataadmin/css/layout.css - bundles/sonataadmin/css/tree.css - bundles/sonataadmin/css/colors.css …
  108. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css - bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css - bundles/sonatacore/vendor/select2/select2.css - bundles/sonataadmin/css/styles.css - bundles/sonataadmin/css/layout.css - bundles/sonataadmin/css/tree.css - bundles/sonataadmin/css/colors.css … https://sonata-project.org/bundles/admin/master/doc/reference/configuration.html
  109. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css - bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css - bundles/sonatacore/vendor/select2/select2.css - bundles/sonataadmin/css/styles.css - bundles/sonataadmin/css/layout.css - bundles/sonataadmin/css/tree.css - bundles/sonataadmin/css/colors.css - css/styles.css …
  110. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css - bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css - bundles/sonatacore/vendor/select2/select2.css - bundles/sonataadmin/css/styles.css - bundles/sonataadmin/css/layout.css - bundles/sonataadmin/css/tree.css - bundles/sonataadmin/css/colors.css - css/styles.css …
  111. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css - bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css - bundles/sonatacore/vendor/select2/select2.css - bundles/sonataadmin/css/styles.css - bundles/sonataadmin/css/layout.css - bundles/sonataadmin/css/tree.css - bundles/sonataadmin/css/colors.css - css/styles.css …
  112. Customizing the styles # app/config/config.php sonata_admin: title: SunshinePHP title_logo: 'img/logo.png'

    assets: stylesheets: - bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css - bundles/sonatacore/vendor/ionicons/css/ionicons.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css - bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css - bundles/sonataadmin/vendor/iCheck/skins/square/blue.css SonataAdminBundle uses AdminLTE, good to have a look there https://almsaeedstudio.com/
  113. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  114. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: 'SonataAdminBundle::standard_layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  115. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: ':Admin:layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  116. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: ':Admin:layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  117. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: ':Admin:layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: 'SonataAdminBundle:Core:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  118. Overriding some key templates # app/config/config.php sonata_admin: templates: user_block: 'SonataAdminBundle:Core:user_block.html.twig'

    add_block: 'SonataAdminBundle:Core:add_block.html.twig' layout: ':Admin:layout.html.twig' ajax: 'SonataAdminBundle::ajax_layout.html.twig' dashboard: ':Admin:dashboard.html.twig' search: 'SonataAdminBundle:Core:search.html.twig' list: 'SonataAdminBundle:CRUD:list.html.twig' show: 'SonataAdminBundle:CRUD:show.html.twig' show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig' edit: 'SonataAdminBundle:CRUD:edit.html.twig' ...
  119. Creating two Admins for the same Entity # src/AppBundle/ProcessedGiftAdmin.php class

    ProcessedGiftAdmin extends Admin { } Sure. We just create a new Admin class...
  120. Creating two Admins for the same Entity # src/AppBundle/ProcessedGiftAdmin.php class

    ProcessedGiftAdmin extends Admin { protected $baseRouteName = 'processed_gift'; protected $baseRoutePattern = 'processed-gift' ... } (adding these properties for the routing)
  121. Creating two Admins for the same Entity # app/config/services.yml services:

    admin.gift: class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, AppBundle:GiftAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Gifts } calls: - [ setTemplate, [edit, :Admin:edit_gift.html.twig]] ...and register the new service.
  122. Creating two Admins for the same Entity # app/config/services.yml services:

    admin.gift: class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, AppBundle:GiftAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Gifts } calls: - [ setTemplate, [edit, :Admin:edit_gift.html.twig]] admin.processed_gift: class: AppBundle\Admin\ProcessedGiftAdmin arguments: [~, AppBundle\Entity\Gift, ~] tags: - { name: sonata.admin, manager_type: orm, label: Processed } ...and register the new service.
  123. Creating two Admins for the same Entity # app/config/services.yml services:

    admin.gift: class: AppBundle\Admin\GiftAdmin arguments: [~, AppBundle\Entity\Gift, AppBundle:GiftAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Active } calls: - [ setTemplate, [edit, :Admin:edit_gift.html.twig]] admin.processed_gift: class: AppBundle\Admin\ProcessedGiftAdmin arguments: [~, AppBundle\Entity\Gift, ~] tags: - { name: sonata.admin, manager_type: orm, label: Processed } ...and register the new service.
  124. Admin as a child in some other Admin’s menu #

    app/config/services.yml admin.buyer: class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers } calls: - [ addChild, [ '@admin.payment' ] ] admin.payment: class: AppBundle\Admin\PaymentAdmin arguments: [~, AppBundle\Entity\Payment, ~] tags: - { name: sonata.admin, manager_type: orm, label: Payments }
  125. Admin as a child in some other Admin’s menu #

    app/config/services.yml admin.buyer: class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers } calls: - [ addChild, [ '@admin.payment' ] ] admin.payment: class: AppBundle\Admin\PaymentAdmin arguments: [~, AppBundle\Entity\Payment, ~] tags: - { name: sonata.admin, manager_type: orm, label: Payments }
  126. Admin as a child in some other Admin’s menu #

    app/config/services.yml admin.buyer: class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers } calls: - [ addChild, [ '@admin.payment' ] ] admin.payment: class: AppBundle\Admin\PaymentAdmin arguments: [~, AppBundle\Entity\Payment, ~] tags: - { name: sonata.admin, manager_type: orm, label: Payments }
  127. Admin as a child in some other Admin’s menu #

    app/config/services.yml admin.buyer: class: AppBundle\Admin\BuyerAdmin arguments: [~, AppBundle\Entity\Buyer, ~] tags: - { name: sonata.admin, manager_type: orm, label: Buyers } calls: - [ addChild, [ '@admin.payment' ] ] admin.payment: class: AppBundle\Admin\PaymentAdmin arguments: [~, AppBundle\Entity\Payment, ~] tags: - { name: sonata.admin, manager_type: orm, label: Payments }
  128. Admin as a child in some other Admin’s menu #

    src/AppBundle/Admin/BuyerAdmin.php protected function configureSideMenu(ItemInterface $menu, $action, AdminInterface $childAdmin = null) { ... $menu->addChild( 'Payments', $admin->generateMenuUrl('admin.buyer|admin.payment.list', array('id' => $id)) ); }
  129. Admin as a child in some other Admin’s menu #

    src/AppBundle/Admin/BuyerAdmin.php protected function configureSideMenu(ItemInterface $menu, $action, AdminInterface $childAdmin = null) { ... $menu->addChild( 'Payments', $admin->generateMenuUrl('admin.buyer|admin.payment.list', array('id' => $id)) ); }
  130. Admin as a child in some other Admin’s menu #

    src/AppBundle/Admin/PaymentAdmin.php public function configure() { $this->parentAssociationMapping = 'buyer'; }
  131. Linking a custom action from the sidebar We create our

    custom action, the template, and the route...
  132. Linking a custom action from the sidebar We create our

    custom action, the template, and the route... # app/config/config.yml dashboard: groups: ... demo.admin.settings: label: Settings label_catalogue: AppBundle icon: '<i class="fa fa-gear"></i>' items: - admin.config ...and link it from the sidebar
  133. Linking a custom action from the sidebar We create our

    custom action, the template, and the route... # app/config/config.yml dashboard: groups: ... demo.admin.settings: label: Settings label_catalogue: AppBundle icon: '<i class="fa fa-gear"></i>' items: - admin.config - route: config_myEdit label: 'My config' ...and link it from the sidebar
  134. Your entire application can be “inside” Sonata Your project can

    have parts that are not an admin panel But are perfectly integrated with everything else You can actually put whatever you want in there
  135. Your entire application can be “inside” Sonata Create your action

    in your controller (PatientAdminController.php)
  136. Your entire application can be “inside” Sonata Create your action

    in your controller (PatientAdminController.php) Configure the new route in configureRoutes (PatientAdmin.php) $collection->add('editor', $this->getRouterIdParameter().'/editor');
  137. Your entire application can be “inside” Sonata Create your action

    in your controller (PatientAdminController.php) Configure the new route in configureRoutes (PatientAdmin.php) $collection->add('editor', $this->getRouterIdParameter().'/editor'); Do setTemplate in the service configuration (services.yml) admin.patient: class: AppBundle\Admin\PatientAdmin arguments: [~, AppBundle\Entity\Patient, AppBundle:PatientAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Activos } calls: - [ setTemplate, [edit, :Admin:my_template.html.twig]]
  138. Your entire application can be “inside” Sonata Create your action

    in your controller (PatientAdminController.php) Configure the new route in configureRoutes (PatientAdmin.php) $collection->add('editor', $this->getRouterIdParameter().'/editor'); Do setTemplate in the service configuration (services.yml) admin.patient: class: AppBundle\Admin\PatientAdmin arguments: [~, AppBundle\Entity\Patient, AppBundle:PatientAdmin] tags: - { name: sonata.admin, manager_type: orm, label: Activos } calls: - [ setTemplate, [edit, :Admin:my_template.html.twig]] All stuff that we already know!
  139. FOSUserBundle & SonataUserAdminBundle SonataUserAdminBundle works as a layer on top

    of FOSUserBundle It gives you some additional functionalities But you don’t actually need it, if you don’t want to use it https://sonata-project.org/bundles/user/master/doc/reference/introduction.html
  140. Security, roles Security can be configured in different ways /

    levels With as much detail as you want https://sonata-project.org/bundles/admin/master/doc/reference/security.html
  141. Events There are some events defined in Sonata that can

    be very handy: • sonata.admin.event.persistence.pre_update • sonata.admin.event.persistence.post_update • sonata.admin.event.persistence.pre_persist • sonata.admin.event.persistence.post_persist • sonata.admin.event.persistence.pre_remove • sonata.admin.event.persistence.post_remove https://sonata-project.org/bundles/admin/master/doc/reference/events.html
  142. Final thoughts Has solved problems historically attributed, keeps improving Allows

    you to efficiently solve a tedious problem In my experience, a good decision from the business point of view
  143. Final thoughts 1. It gives you a lot for free

    2. You can override whatever you want and do your own thing