📜 ⬆️ ⬇️

KnpMenuBundle + Sonata. Making a menu from the base

All the pleasant time of the day dear habrovchane. I love symfony. I like her and I adore her. I also like SonataAdminBundle. I think many of you too. So, in this article I want to consider the process of creating a menu for a site when KNPMenuBundle + SonataAdminBundle is involved in this process. In fact, the menu creation process is fairly simple and is described in detail on the github'e of the bundle itself, but what if we need the menu to be controlled from the admin area? Interested? Then I ask for cat.

I just want to excuse for what is stated below, but I did not find a clear explanation of how to do this. If anyone saw you like them, share the link. Maybe some of my article is useful and will be useful. So proceed. Initially, I assume that you already have a Sonata installed and it works. So, first of all, let's start with generating a bundle for our menu.

Open the console, go to the folder with the project and write:
#php app / console generate: bundle

You are free to choose the name for the bundle yourself, but I called it just MenuBundle.
After it is necessary to create 2 entities. If you did not have it in the folder with the folder of the Entity folder, then create it. So, the file number of times - Menu.php. File number two - MenuType.php. What is the second file for, I will explain later.
')
I give the source code of the file at the number of times:

namespace MyFolder\MenuBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="menu") * @ORM\Entity(repositoryClass="MyFolder\MenuBundle\Entity\MenuRepository") */ class Menu{ /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=100) */ protected $title; /** * @ORM\Column(type="string", length=100) */ protected $route; /** * @ORM\Column(type="string", nullable=true) */ protected $alias; /** * @ORM\Column(type="boolean") */ protected $static; /** * @ORM\ManyToOne(targetEntity="MyFolder\MenuBundle\Entity\MenuType", inversedBy="menuTypeId") * @ORM\JoinColumn(name="menuTypeId", referencedColumnName="id") */ protected $menuTypeId; } 


Look at file number two:
 namespace MyFolder\MenuBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="menu_type") * @ORM\Entity(repositoryClass="MyFolder\MenuBundle\Entity\MenuTypeRepository") */ class MenuType { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=100) */ protected $title; /** * @ORM\OneToMany(targetEntity="Menu", mappedBy="menuTypeId") */ private $typeId; } 


So, we have 2 models written, let's generate getters and setters for them ?!
# php app / console doctrine: generate: entities MyFolder / MenuBundle / Entity / Menu
and
# php app / console doctrine: generate: entities MyFolder / MenuBundle / Entity / MenuType

If everything went well, your classes should be transformed and get their own getters and setters.

After you need to create the tables themselves in the database.
# php app / console doctrine: schema: update --force

So we have 2 tables related to each other by ManyToOne. That is, in fact, the table ONCE can have many links to table TWO.

A small retreat. Let's hear those connections in the models, for those who do not know.

Below the lines from the file ONCE.
 /** * @ORM\ManyToOne(targetEntity="MyFolder\MenuBundle\Entity\MenuType", inversedBy="menuTypeId") * @ORM\JoinColumn(name="menuTypeId", referencedColumnName="id") */ protected $menuTypeId; 


They tell us that many lines from the file MyFolder \ MenuBundle \ Entity \ Menu can refer to only one line from the file MyFolder \ MenuBundle \ Entity \ MenuType, which we kindly report the annotation from the file TWO
 /** * @ORM\OneToMany(targetEntity="Menu", mappedBy="menuTypeId") */ private $typeId; 


Thus, this is one of the ways to establish relationships between entities in symfony.

Go back to the code. So, we prepared the entities, created the database. Go to the admin part.

In order for our admin panel to work, we will do the following. In the folder with bandl create folder Admin and in it 2 files. File times - MenuAdmin, file two - MenuTypeAdmin. Code from file ONCE:
 namespace MyFolder\MenuBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Show\ShowMapper; class MenuAdmin extends Admin{ protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('title', null, array()) ->add('route', null, array()) ->add('alias', null, array()) ->add('static', null, array('required' => false)) ->add('menuTypeId', 'sonata_type_model', array( 'class'=>'MenuBundle:MenuType', 'property'=>'title', 'required' => false ) ) ; } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('title', null, array()) ->add('id', null, array()) ->add('route', null, array()) ; } public function configureShowField(ShowMapper $showMapper){ $showMapper ->add('title', null, array()) ->add('id', null, array()) ->add('route', null, array()) ; } protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('title', null, array()) ->add('route', null, array()) ->add('id', null, array()) ->add('menuTypeId', 'entity', array( 'class'=>'MenuBundle:MenuType', 'property'=>'title' ) ) ; } } 


Immediately I apologize, but I will not write here what this or that line means, because the article is not devoted to Sonata. Maybe in the future and sign for if the need arises.

The code from the TWO file:
 namespace MyFolder\MenuBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Show\ShowMapper; class MenuTypeAdmin extends Admin{ protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('title', null, array()) ; } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('title', null, array()) ->add('id', null, array()) ; } public function configureShowField(ShowMapper $showMapper){ $showMapper ->add('title', null, array()) ->add('id', null, array()) ; } protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('title', null, array()) ->add('id', null, array()) ; } } 


Next, you need to tell the sonata that she should see our folder and our 2 files. For this you need to register a service. Open the file MyFolder / MenuBundle / Resources / config / services.yml and make changes. I will give the code for the entire file so that no inconsistencies arise:
 parameters: services: admin.menu: class: MyFolder\MenuBundle\Admin\MenuAdmin tags: - { name: sonata.admin, manager_type: orm, group: , label: } arguments: [null, MyFolder\MenuBundle\Entity\Menu, SonataAdminBundle:CRUD] admin.menu_type: class: MyFolder\MenuBundle\Admin\MenuTypeAdmin tags: - { name: sonata.admin, manager_type: orm, group:  , label:  } arguments: [null, MyFolder\MenuBundle\Entity\MenuType, SonataAdminBundle:CRUD] 


So, if you did everything correctly, then 2 items should appear in the admin part of your site, and they would look something like this:
image

If this does not work out, you can write to me (sin666m4a1fox@gmail.com), I will gladly answer your letters.

So, if you do everything correctly, now you have the opportunity to add, change, delete your menu items.

Why such complexity you ask? Probably it would be worth starting with this, but nevertheless it’s not just that, so we’ve made this journey with you. Actually the process of creating such a menu is aimed at the simplicity of its subsequent modification and addition of new items, displaying the overall page template and its different contents ... and many, many more.

We continue. If you did everything correctly, then I dare you to suggest creating the first menu type. To do this, go to the tab “Menu Type” and press the button with a plus sign. So everything just has to be. One field is the title. Thus, we can create Menu Types to which we can later bind the Menu Items. I made two types of menus (“Main Menu” and “Menu in the Basement”). Then go to the menu itself and add a new one. Here is more interesting.
image

Actually Title is understandable why, Route is the link that will go to the KNPMenuBundle. Alias ​​is my personal preference, you can not make such a point. Static checkbox is designed to tell the system that the page will be custom and it will not need the Action method in the controller. Menu Type Id is actually there and those menu items that you created above will appear. This is the very binding, which later will help the system to understand which menu item you still choose to choose in this or that case.

One moment. In the need to create a custom route, I had to apply this JS code.
 $(document).ready(function () { $('input[id$="_static"]').click(function(){ var $_thisRoute = $('input[id$="_route"]'), defaultValues = $_thisRoute.val().split('/'); if($(this).is(':checked')) { $_thisRoute.val('/custom/'+defaultValues[defaultValues.length -1]); } else { $_thisRoute.val('/'+defaultValues[defaultValues.length -1]); } }) }); 


That is, when you click on the Static item, the route is transformed, if you wrote for example about-us, then it becomes / custom / about-us.

How to add your js to the admin part of the sonata will not be painted, it goes beyond the scope of the area in question, if necessary, I will, just ask :)

I created 7 menu items

image

As you can see, Route is almost the same for everyone except the last item. All this is tied only to the Main Menu. Actually we are finished with this part. Moves to KNPMenuBundle.

I installed the version 1.1 of the bundle, despite the fact that I already have 2.2, but I did not succeed in making friends with 2.2 Sonata, and in the Sonata in the requirements there is a version of KNPMenuBundle 1.1 so we do not break anything.

We continue. In the folder with our bundle we create the Menu folder in it the Builder.php file. Here is his code:
 namespace MyFolder\MenuBundle\Menu; use Knp\Menu\FactoryInterface; use Knp\Menu\ItemInterface; use Symfony\Component\DependencyInjection\ContainerAware; class Builder extends ContainerAware { public function mainMenu(FactoryInterface $factory, array $options) { $menuItems = $this->container->get('menu')->getMainMenu(); $menu = $factory->createItem('root'); $this->setCurrentItem($menu); $menu->setChildrenAttribute('class', 'nav'); $menu->setExtra('currentElement', 'active'); foreach($menuItems as $item) { $menu->addChild($item->getTitle(), array('uri' => $item->getRoute())); } return $menu; } protected function setCurrentItem(ItemInterface $menu) { $menu->setCurrentUri($this->container->get('request')->getPathInfo()); } } 


Here are a couple of moments. Since Builder itself inherits ContainerAware, we obviously have the opportunity to use $ this-> container-> get (), and if so, we can quickly write a service to select the necessary menu items. No sooner said than done.

In the bundle folder, create the Service folder and in it one MenuService.php file. Before starting to write code to it, let's make the service available, that is, edit the file MyFolder / MenuBundle / Resources / config / services.yml so that we would have the following:
 parameters: services: menu: class: MyFolder\MenuBundle\Service\MenuService arguments: [@service_container] admin.menu: class: MyFolder\MenuBundle\Admin\MenuAdmin tags: - { name: sonata.admin, manager_type: orm, group: , label: } arguments: [null, MyFolder\MenuBundle\Entity\Menu, SonataAdminBundle:CRUD] admin.menu_type: class: MyFolder\MenuBundle\Admin\MenuTypeAdmin tags: - { name: sonata.admin, manager_type: orm, group:  , label:  } arguments: [null, MyFolder\MenuBundle\Entity\MenuType, SonataAdminBundle:CRUD] 


Actually now the code of the file MyFolder / MenuBundle / Service / MenuService
 namespace MyFolder\MenuBundle\Service; use Symfony\Component\DependencyInjection\Container; class MenuService { private $doctrine; private $container; private $menuRepository; public function __construct(Container $container) { $this->container = $container; $this->doctrine = $this->container->get('doctrine'); $this->menuRepository = $this->doctrine->getRepository('MenuBundle:Menu'); } public function getMainMenu() { return $this->menuRepository->getMainMenu(); } } 


I will remind one line from the Menu.php entity
ORM \ Entity (repositoryClass = “MyFolder \ MenuBundle \ Entity \ MenuRepository")
This means that in the folder with the entity, create the MenuRepository.php file and the code in it looks like this:
 namespace MyFolder\MenuBundle\Entity; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; class MenuRepository extends EntityRepository { public function getMainMenu() { return $this->findBy(array('menuTypeId' => 1)); } } 


In fact, this is the very sample that will return all menu items that relate only to the “Main Menu” type.

We finish: now to display our menu, in the twig template it is enough to register such a line:
 {{ knp_menu_render('MenuBundle:Builder:mainMenu', { 'currentClass': 'active'}) }} 


The CSS class active will have a value that is currently active. If you made a menu like me, then you can do this at one of your controllers.
 /** * @Template() * @Route("/custom/{link}", name="_custom_page", defaults={"link" = "/"}) */ public function customAction($link) { return $this->render('CommonBundle:Default:commonPage.html.twig', array('page' => $link)); } 


This code is not a panacea, just as an example. But for the last menu item, you will need to create your own method.

If it seemed to you that the article only complicates everything, and there is a way to write it easier or faster, please share it with me, for first learning any framework we like to complicate things.

PS If something is not correctly explained or what is not explained, please write to the post office, I will gladly answer all. Thanks for attention. See you.

Source: https://habr.com/ru/post/202072/


All Articles