Buy

What else can we do with the menu? Well, of course, we can expand the simple entity items to get more control. To expand it, add entity: User. Now we can go crazy with label: Users:

227 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { entity: 'User', label: 'Users' }
... lines 98 - 227

Remember, you can also specify the label under the User entity itself. If we added a label key here, it would apply to the menu, the header to that section and a few other places. But under menu, it just changes the menu text.

Do the same thing for GenusNote, with label: Notes, and also for sub families: entity: SubFamily, label: Sub-Families:

227 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 99
- { entity: 'GenusNote', label: 'Notes' }
- { entity: 'SubFamily', label: 'Sub-Families' }
... lines 102 - 227

At this point, it should be no surprise that we can control the icon for each menu. Like, icon: user, icon: sticky-note and icon: '':

227 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { entity: 'User', label: 'Users', icon: 'user' }
... lines 98 - 99
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' }
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' }
... lines 102 - 227

Before configuring anything, each item has a little arrow icon. With empty quotes, even that icon is gone.

Adding Menu Separators & Sub-Menus

Oh, but the fanciness does, not, stop! The menu does a lot more than just simple links: it has separators, groups and sub-links. Above Genus, create a new item that only has a label:

228 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { entity: 'User', label: 'Users', icon: 'user' }
- { label: 'Genus' }
... lines 99 - 228

Yep, this has no route key and no entity key. We're not linking to anything.

Instead, this just adds a nice separator. Or, you can go a step further and create a sub-menu. Change this new menu item to use the expanded format. Then, add a children key. Indent all the other links so that they live under this:

231 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { entity: 'User', label: 'Users', icon: 'user' }
-
label: 'Genus'
children:
- Genus
- GenusHorde
... line 103
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' }
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' }
... lines 106 - 231

And just to make it even nicer, add a separator called Related:

231 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { entity: 'User', label: 'Users', icon: 'user' }
-
label: 'Genus'
children:
- Genus
- GenusHorde
- { label: 'Related' }
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' }
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' }
... lines 106 - 231

Try it! Try it! Nice! The Genus menu expands to show the sub-items and the Related separator.

We can also link to the different entity sections, but with different sort options. We already have a Genus link that will take us to that page with the normal sort. But let's not limit ourselves! We could also add another link to that same section, with a different label: Genuses (sorted by ID) and a params key:

238 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 97
-
label: 'Genus'
children:
- Genus
-
entity: 'Genus'
label: 'Genuses (sorted by ID)'
params:
... lines 106 - 238

Here, we can control whatever query parameters we want, like sortField: id, sortDirection: ASC and... heck pizza: delicious:

238 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 97
-
label: 'Genus'
children:
- Genus
-
entity: 'Genus'
label: 'Genuses (sorted by ID)'
params:
sortField: 'id'
sortDirection: 'ASC'
pizza: 'delicious'
... lines 109 - 238

That last query parameter won't do anything... but it doesn't make it any less true!

Ok, refresh! Then try out that new link. Yea! We're sorting by id and you might also notice in the address bar that pizza=delicious.

On that note, one of the other query parameters is action, which we can also set to anything. Copy this entire new menu link and - at the top of children - paste it. This time, let's link to the show page of 1 specific genus... our favorite "Pet genus". To do that, set action to show and id to some id in the database, like 2:

245 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 97
-
label: 'Genus'
children:
-
entity: 'Genus'
label: 'Pet genus'
icon: 'paw'
params:
action: 'show'
id: 2
- Genus
... lines 109 - 245

This isn't anything special, we're just taking advantage of how the query parameters work in EasyAdminBundle.

And while we're here, it might also be nice to add a link to the front-end of our app. This is also nothing special: add a new link that points to the app_genus_list route called "Open front-end":

246 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... line 96
- { label: 'Open front-end', route: 'app_genus_list' }
... lines 98 - 246

Refresh! And try that link. Nice!

In addition to routes, if you want, you can just link to external URLs. Go to the bottom of the list... and make sure we're at the root level. Add a new section called "Important stuff" with icon: explanation and a children key:

258 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 120
-
label: 'Important stuff'
icon: 'exclamation'
children:
... lines 125 - 258

I'll paste a couple of very important external links for silly kittens and wet cats:

258 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... line 82
design:
... lines 84 - 94
menu:
... lines 96 - 120
-
label: 'Important stuff'
icon: 'exclamation'
children:
-
label: 'Silly kittens'
url: 'https://www.youtube.com/results?search_query=silly+kittens'
target: '_blank'
-
label: 'Wet cats'
url: 'http://www.boredpanda.com/funny-wet-cats/'
target: '_blank'
... lines 133 - 258

Yep, instead of entity or route keys, you can skip all of that and just add url. And of course, you can set the target on any item.

Re-organizing the Config

Ok team, our admin menu is complete! The last thing I want to show you isn't anything special to this bundle: it's just a nice way to organize any configuration. In fact, this trick will become the standard way to organize things in Symfony 4.

Right now, well, our admin configuration goes from line 81 of config.yml to line

  1. Wow! It's huge!

To clear things up, I'd like to create a new file called admin.yml. Copy all of this config, remove it, and add it to admin.yml:

177 lines app/config/admin.yml
easy_admin:
site_name: 'Aqua<i>Note</i>'
design:
brand_color: '#81b9ba'
assets:
css: ['css/custom_backend.css']
js:
- 'https://unpkg.com/snarkdown@1.2.2/dist/snarkdown.umd.js'
- 'js/custom_backend.js'
templates:
field_id: 'admin/fields/_id.html.twig'
form_theme:
- horizontal
- easy_admin/_form_theme.html.twig
menu:
- { label: 'Dashboard', route: 'admin_dashboard', default: true }
- { label: 'Open front-end', route: 'app_genus_list' }
- { entity: 'User', label: 'Users', icon: 'user' }
-
label: 'Genus'
children:
-
entity: 'Genus'
label: 'Pet genus'
icon: 'paw'
params:
action: 'show'
id: 2
- Genus
-
entity: 'Genus'
label: 'Genuses (sorted by ID)'
params:
sortField: 'id'
sortDirection: 'ASC'
pizza: 'delicious'
- GenusHorde
- { label: 'Related' }
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' }
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' }
-
label: 'Important stuff'
icon: 'exclamation'
children:
-
label: 'Silly kittens'
url: 'https://www.youtube.com/results?search_query=silly+kittens'
target: '_blank'
-
label: 'Wet cats'
url: 'http://www.boredpanda.com/funny-wet-cats/'
target: '_blank'
list:
title: 'List of %%entity_label%%'
actions: ['show', 'export']
entities:
Genus:
class: AppBundle\Entity\Genus
controller: AppBundle\Controller\EasyAdmin\GenusController
label: Genuses
help: Genuses are not covered under warranty!
list:
help: Do not feed!
actions:
- { name: 'edit', icon: 'pencil', label: 'Edit' }
- { name: 'show', icon: 'info-circle', label: '' }
fields:
- 'id'
- 'name'
- 'isPublished'
- { property: 'firstDiscoveredAt', format: 'M Y', label: 'Discovered' }
- 'funFact'
- { property: 'speciesCount', format: '%b' }
sort: 'name'
search:
help: null
fields: ['id', 'name']
show:
actions:
-
name: 'genus_feed'
type: 'route'
label: 'Feed genus'
css_class: 'btn btn-info'
icon: 'cutlery'
- { name: 'changePublishedStatus', css_class: 'btn' }
# templates:
# field_id: 'admin/fields/_id.html.twig'
form:
fields:
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' }
- name
- speciesCount
- { property: 'firstDiscoveredAt', type_options: { widget: 'single_text' }}
- { property: 'subFamily', type: 'easyadmin_autocomplete' }
- { type: 'section', label: 'Optional' }
- { property: 'funFact', type: 'textarea', css_class: 'js-markdown-input' }
- isPublished
- { type: 'group', css_class: 'col-sm-6', label: 'Studied by ...' }
-
property: 'genusScientists'
type: 'text'
type_options:
mapped: false
attr: { class: 'js-genus-scientists-field' }
-
type: 'group'
css_class: 'col-sm-6 new-row'
label: 'Identification'
icon: 'id-card-o'
help: 'For administrators'
-
property: id
type_options: {disabled: true}
-
property: 'slug'
help: 'unique auto-generated value'
type_options: { disabled: true }
new:
fields:
- '-id'
- '-slug'
GenusHorde:
class: AppBundle\Entity\Genus
label: HORDE of Genuses 😱 !!!
list:
dql_filter: 'entity.speciesCount >= 50000 AND entity.isPublished = true'
sort: 'speciesCount'
help: Run for your life!!! 😱😱😱
search:
dql_filter: 'entity.speciesCount >= 50000'
GenusNote:
class: AppBundle\Entity\GenusNote
label: Genus Notes
list:
title: List of notes
fields:
- id
- username
- { property: 'userAvatarFilename', label: 'User avatar', type: 'image', base_path: '/images/', sortable: false }
- createdAt
- genus
sort: ['genus.name', 'ASC']
SubFamily:
class: AppBundle\Entity\SubFamily
list:
actions: ['-show']
disabled_actions: ['show']
User:
class: AppBundle\Entity\User
controller: AppBundle\Controller\EasyAdmin\UserController
list:
fields:
- id
- { property: 'email', type: 'email' }
- { property: 'isScientist', label: 'Is scientist?', type: 'boolean' }
- { property: 'fullName', template: '_field_user_full_name.html.twig' }
- { property: 'avatarUri', type: 'url' }
form:
fields:
- { type: 'section', label: 'User details' }
- { property: 'fullName', type: 'text', help: 'First then Last' }
- { type: 'divider' }
- avatarUri
-
type: 'section'
label: 'Contact information'
icon: 'phone'
help: 'Only for emergencies!'
css_class: 'any-you-want'
- email
- { type: 'section', label: 'Education' }
- isScientist
- universityName

Perfect!

Now, we just need to make sure that Symfony loads this file. At the top of config.yml, just load another resource: admin.yml:

81 lines app/config/config.yml
imports:
... lines 2 - 4
- { resource: admin.yml }
... lines 6 - 81

And that is it! When we refresh, everything still works!

Phew, we're done! EasyAdminBundle is great. But of course, depending on how custom you need things, you might end up overriding a lot of different parts. Many things can be done via configuration. But by using the tools that we've talked about, you can really override everything. Ultimately, customizing things will still be a lot faster than building all of this on your own.

All right guys, thank you so much for joining me! And a huge thanks to my co-author Andrew for doing all the actual hard work.

Ok, seeya next time!

Leave a comment!

  • 2017-10-09 Diego Aguiar

    Hey @axa

    Well, that's true, you never know what will be the next thing that your client will ask, but if your system doesn't heavily deppends on multiple Roles/Permissions, you could give it a try

    Have a nice day

  • 2017-10-09 axa

    Hello Victor,
    I am reading last Javier's comment :(
    Yes, Javier also said: "that this bundle supports only 80% of features. if you need 100% better to use Sonata." .
    Issue is : you cannot know in advance what will need. For example, after a year , client can demand something and that can be nightmare.
    Looks that better to avoid this bundle :(
    Back to ugly sonata.

  • 2017-10-09 Victor Bocharsky

    Hey Axa,

    Really good question. Please, take a look at Javier's comment here: https://github.com/javiereg... - I think it will answer your question. In shorts, you can use CSS tricks to implement it, or by overriding EasyAdminBundle templates, where you can use is_granted() Twig function and do whatever you want. But for more complex things better use SonataAdminBundle instead.

    Cheers!

  • 2017-10-09 axa

    Thanks for a great tutorial.
    One question about menu: if we have admin and moderator (different roles), could we hide some menu items for Moderator (only admin can see all menu items)?

  • 2017-10-05 Diego Aguiar

    Hey Tanariel

    Thanks for your kind words, we are very happy to hear you like our tutorials :)

    Cheers!

  • 2017-10-05 Tanariel

    Thanks very much for such a great tutorial :)

  • 2017-08-07 Entwickler

    Thank you very much @Victor Bocharsky

    I started Symfony RESTful API course.

  • 2017-08-03 Victor Bocharsky

    Hey Entwickler ,

    Yes, sounds correct. Or maybe you even can implement Mobile App which is a wrapper for browser, so you will load this entire EasyAdmin dashboard, then you don't need to implement API endpoints at all. EasyAdmin responsive design is pretty good for mobile devices. Actually, it depends on your needs.

    But what about API, we have an entire track about RESTful APIs, I think it all can be useful for you more or less, it's a complex question, you know. Just depends on how strong your knowledges about it. Check it out here: https://knpuniversity.com/t...

    There're also a Symfony-related RESTful APIs screencasts where you can find some useful bundles as well: https://knpuniversity.com/t...

    Cheers!

  • 2017-08-02 Entwickler

    Hello Ryan,

    Thank you very much for this nice tutorial.

    I have a small (or big) question:

    When I implement such an Application with EasyAdmin Bundle, I would like to make Mobile Apps with Ionic (2 or 3) to connect to the implemented EasyAdmin Bundle Web-App. I can imagine that I need to implement an API for data flow for Mobile Apps.

    Which Knp University tutorial(s) would you suggest to implement an API to use with third party Frameworks to build Mobile Apps based on EasyAdmin Bundle?

    Thanks in advance.