Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Having Fun with the Menu

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our menu on the left is getting a little long and... kind of confusing... since we now have two question links. To make this more user-friendly, let's divide this into a sub-menu. We do that inside of DashboardController... because that's, of course, where we configure the menu items.

Adding a Sub Menu

To create a sub-menu, say yield MenuItem::subMenu() and then give that a name - like Questions - and an icon... just like we do with normal menu items.

To populate the items in this menu, say ->setSubItems(), pass this an array, and then we'll wrap our other two menu item objects inside of this. Of course, now we need to indent, remove the yield, and... replace the semicolons with commas.

Perfect! Now change Questions to... how about All... and let's play with the icons. Change the first to fa fa-list... and the second to fa fa-warning.

... lines 1 - 24
class DashboardController extends AbstractDashboardController
{
... lines 27 - 57
public function configureMenuItems(): iterable
{
... line 60
yield MenuItem::subMenu('Questions', 'fa fa-question-circle')
->setSubItems([
MenuItem::linkToCrud('All', 'fa fa-list', Question::class)
->setController(QuestionCrudController::class)
->setPermission('ROLE_MODERATOR'),
MenuItem::linkToCrud('Pending Approval', 'fa fa-warning', Question::class)
->setPermission('ROLE_MODERATOR')
->setController(QuestionPendingApprovalCrudController::class),
]);
... lines 70 - 73
}
... lines 75 - 134
}

Let's try that. Move over... refresh and... ahhh, much cleaner!

But wait, there's more we can do with the menu... like adding separators... technically called "sections". Right after linkToDashboard(), add yield MenuItem::section() and pass it Content.

... lines 1 - 57
public function configureMenuItems(): iterable
{
... line 60
yield MenuItem::section('Content');
... lines 62 - 75
}
... lines 77 - 138

Let's put one more down here - yield MenuItem::section()... but this time leave the label blank. So unlike sub-menus, which wrap menu items, you can just pop a section anywhere that you want a separator.

... lines 1 - 57
public function configureMenuItems(): iterable
{
... lines 60 - 72
yield MenuItem::linkToCrud('Users', 'fas fa-users', User::class);
yield MenuItem::section();
yield MenuItem::linkToUrl('Homepage', 'fas fa-home', $this->generateUrl('app_homepage'));
}
... lines 77 - 138

Let's go check it out. Refresh and... very nice! Separator one says "Content"... and separator two gives us a little gap without any text.

We saw earlier that you can add menu links to point to a dashboard, other CRUD sections... or just any URL you want, like the Homepage. So, not surprisingly, you can also link to external sites. For instance, let's say that I love StackOverflow so much, that I want to link to it. We can tweak the icons, and for the URL, pass whatever you want, like https://stackoverflow.com.

... lines 1 - 57
public function configureMenuItems(): iterable
{
... lines 60 - 75
yield MenuItem::linkToUrl('StackOverflow', 'fab fa-stack-overflow', 'https://stackoverflow.com');
}
... lines 78 - 139

Oh, but let me fix my icon name. Great! Now when we refresh... no surprise, that works fine.

More Menu Item Options

If you look closer at these menu items, you'll see that they have a lot of options on them! We know we have things like setPermission() and setController(), but we also have methods like setLinkTarget(), setLinkRel(), setCssClass(), or setQueryParameter(). For this case, let's ->setLinkTarget('_blank')... so that now if I click "StackOverflow", it pops up in a new tab.

... lines 1 - 57
public function configureMenuItems(): iterable
{
... lines 60 - 75
yield MenuItem::linkToUrl('StackOverflow', 'fab fa-stack-overflow', 'https://stackoverflow.com')
->setLinkTarget('_blank');
}
... lines 79 - 140

Next: what if we need to disable an action on an entity-by-entity basis? Like, we want only want to allow questions to be deleted if they are not approved. Let's dive into that.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}