W poprzednim rozdziale pt: „KNPMENUBUNDLE” opisałem podstawy dotyczące tworzenia statycznego menu. Poszczególne sekcje wprowadzaliśmy manualnie do metody MyMenu w klasie Builder. Teraz zmodyfikujemy klasę tak, aby można było tworzyć menu dynamicznie, czyli generowane z tabeli w bazie danych. Do tego będą nam potrzebne jeszcze dwie klasy np: Menu i MenuRepository.
Zakładam, że masz przygotowaną bazę danych (np: MySQL) i skonfigurowany dostęp do niej, a jak nie, to otwórz plik .env znajdujący się w katalogu głównym frameworka i edytuj zmienną środowiskową DATABASE_URL :
1 |
DATABASE_URL=mysql://username:password@127.0.0.1:3306/dbname |
Następnie wygeneruj bazę z wiersza poleceń:
1 |
$ php bin/console doctrine:database:create |
Jeśli nie instalowałeś jeszcze KnpMenuBUndle, to zrób to teraz:
1 |
$ composer require knplabs/knp-menu-bundle |
Zaktualizuj plik bundles.php (config/bundles.php):
1 2 3 4 5 6 |
<?php return [ // ... Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true], ]; |
Dla przypomnienia – konfiguracja KnpMenuBundle w pliku services.yaml (config/services.yaml) ma postać:
1 2 3 4 5 6 7 8 9 10 11 12 |
services: app.menu_builder: class: App\Menu\Builder arguments: ["@knp_menu.factory"] app.main_menu: class: Knp\Menu\MenuItem factory: ["@app.menu_builder", MyMenu] arguments: ["@request_stack"] tags: - { name: knp_menu.menu, alias: navigator } |
Budowę dynamicznego menu zaczniemy od utworzenia klasy Menu.php (/src/Entity/Menu.php). Pola jakie będą nam potrzebne to:
- id – unikatowy klucz / identyfikator
- parent_id – id rodzica, kategorii głównej / nadrzędnej
- category – nazwa kategorii
- slug – link do strony
- enabled – stan kategorii (widoczna – 1 lub niewidoczna na stronie – 0)
Aby slug generował się automatycznie doinstaluj zachowanie sluggable.
Pole parent_id zawiera id klucza głównego, tak, że kategoria podrzędna tworzy relację ManyToOne z kategorią nadrzędną. Przedstawia to poniższy obrazek. Kolorami zaznaczyłem relacje:
Źródło klasy Menu.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<?php // src/Entity/Menu.php namespace App\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** * @ORM\Table(name="menu") * @ORM\Entity(repositoryClass="App\Repository\MenuRepository") */ class Menu { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $category; /** * @Gedmo\Slug(fields={"category"}) * @ORM\Column(type="string", length=128, unique=true, nullable=true) */ private $slug; /** * @ORM\ManyToOne(targetEntity="Menu") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) */ private $parent; /** * @var bool * @ORM\Column(type="boolean") */ private $enabled; public function getId(): ?int { return $this->id; } public function getCategory(): ?string { return $this->category; } public function setCategory(string $category): self { $this->category = $category; return $this; } public function getSlug(): ?string { return $this->slug; } public function setSlug(?string $slug): void { $this->slug = $slug; } public function getParent() { return $this->parent; } public function setParent($parent) { $this->parent = $parent; return $this; } public function getEnabled(): ?bool { return $this->enabled; } public function setEnabled(?bool $enabled): void { $this->enabled = $enabled; } } |
Zwróć uwagę na budowę relacji ManyToOne pomiędzy id i parent_id za pomocą procedury JoinColumn:
1 2 3 4 5 |
/** * @ORM\ManyToOne(targetEntity="Menu") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) */ private $parent; |
Z gotowej klasy generujemy automatycznie tabelę w bazie danych:
1 |
$ php bin/console doctrine:schema:update --force |
Oczywiście możesz to zrobić manualnie np. za pomocą aplikacji PhpMyAdmin, ale po co marnować cenny czas? 😉
Stwórz MenuRepository.php (/src/Repository/MenuRepository.php):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php // src/Repository/MenuRepository.php namespace App\Repository; use App\Entity\Menu; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Symfony\Bridge\Doctrine\RegistryInterface; class MenuRepository extends ServiceEntityRepository { public function __construct(RegistryInterface $registry) { parent::__construct($registry, Menu::class); } public function findAll() { return $this->createQueryBuilder('m') ->andWhere('m.enabled = :val') ->setParameter('val', 1) ->getQuery() ->getResult() ; } } |
W repozytorium dodałem funkcję findAll, która pobierze wszystkie elementy menu, które (andWhere) są włączone, czyli mają stan enabled = 1.
Na koniec zaktualizujmy zawartość Buildera (src/Menu/Builder.php) dołączając metodę findAll z repozytorium:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?php // src/Menu/Builder.php namespace App\Menu; use Knp\Menu\FactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use App\Repository\MenuRepository; // dołączamy repozytorium class Builder { private $factory; private $repository; /** * @param FactoryInterface $factory */ public function __construct( FactoryInterface $factory, MenuRepository $repository ){ $this->factory = $factory; $this->repository = $repository; } public function MyMenu(RequestStack $requestStack) { $menu = $this->factory->createItem('root'); $repository = $this->repository->findAll(); foreach($repository as $li) { $menu->addChild($li->getCategory(), [ 'route' => 'page_list', 'routeParameters' => [ 'slug' => $li->getSlug() ] ]); } return $menu; } } |
Wprowadź kilka rekordów do tabeli i wyrenderuj menu w szablonie strony:
1 |
{{ knp_menu_render('navigator') }} |
I to wszystko. Więcej informacji o KnpMenuBundle znajdziesz pod adresem: https://symfony.com/doc/current/bundles/KnpMenuBundle/index.html