If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAnother chapter, another problem to solve! I need to hide the delete button on the list page if an entity is published. So... nope! We can't just go into config.yml
and add -delete
. We need to override the list.html.twig
template and take control of those actions manually.
Copy that file. Then, up inside our views
directory, I want to show the other way of overriding templates: by convention. Create a new easy_admin
directory, and paste the template there and... that's it! EasyAdminBundle will automatically know to use our list template.
The toughest thing about overriding a template is... well... figuring out what variables you can use! In list.html.twig
... how about in the content_header
block, add {{ dump() }}
:
... lines 1 - 38 | |
{% block content_header %} | |
{{ dump() }} | |
... lines 41 - 93 | |
{% endblock content_header %} | |
... lines 95 - 245 |
And in _id.html.twig
, do the same:
{{ dump() }}<i class="fa fa-key"></i> {{ value }} |
I want to see what the variables look like in each template.
Ok, refresh the genus list page! Awesome! This first dump is from list.html.twig
. It has the same fields
configuration we've been looking at in the profiler, a paginator
object and a few other things, including configuration for this specific section.
The other dumps come from _id.html.twig
. The big difference is that we're rendering one Genus
each time this template is called. So it has an item
variable set to the Genus
object. That will be super handy. If some of the other keys are tough to look at, remember, a lot of this already lives in the EasyAdminBundle profiler area.
Ok, take out those dumps! So, how can we hide the delete button for published genuses? It's actually a bit tricky.
In list.html.twig
, if you search, there is a variable called _list_item_actions
:
... lines 1 - 94 | |
{% block main %} | |
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} | |
... lines 97 - 189 | |
{% endblock main %} | |
... lines 191 - 244 |
This contains information about the actions that should be rendered for each row. It's used further below, in a block called item_actions
:
... lines 1 - 94 | |
{% block main %} | |
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} | |
<div class="table-responsive"> | |
<table class="table"> | |
... lines 100 - 129 | |
<tbody> | |
{% block table_body %} | |
{% for item in paginator.currentPageResults %} | |
... line 133 | |
<tr data-id="{{ _item_id }}"> | |
... lines 135 - 143 | |
{% if _list_item_actions|length > 0 %} | |
... line 145 | |
<td data-label="{{ _column_label }}" class="actions"> | |
{% block item_actions %} | |
{{ include('@EasyAdmin/default/includes/_actions.html.twig', { | |
... lines 149 - 153 | |
}, with_context = false) }} | |
{% endblock item_actions %} | |
</td> | |
{% endif %} | |
</tr> | |
... lines 159 - 164 | |
{% endfor %} | |
{% endblock table_body %} | |
</tbody> | |
</table> | |
</div> | |
... lines 170 - 189 | |
{% endblock main %} | |
... lines 191 - 244 |
The template it renders - _actions.html.twig
- generates a link at the end of the row for each action.
Let's dump _list_item_actions
to see exactly what it looks like.
Ah, ok! It's an array with 3 keys: edit
, show
and delete
. We need to remove that delete
key, only if the entity is published. But how?
Here's my idea: if we override the item_actions
block, we could remove the delete
key from the _list_item_actions
array and then call the parent item_actions
block. It would use the new, smaller _list_item_actions
.
Start by deleting everything and extending the base layout: @EasyAdmin/default/list.html.twig
... so that we don't need to duplicate everything:
{% extends '@EasyAdmin/default/list.html.twig' %} | |
... lines 2 - 8 |
Next, add block item_actions
and endblock
:
{% extends '@EasyAdmin/default/list.html.twig' %} | |
{% block item_actions %} | |
... lines 4 - 6 | |
{% endblock %} |
Twig isn't really meant for complex logic like removing keys from an array. But, to accomplish our goal, we don't have any other choice. So, set _list_item_actions = _list_item_actions|filter_admin_actions(item)
:
{% extends '@EasyAdmin/default/list.html.twig' %} | |
{% block item_actions %} | |
{% set _list_item_actions = _list_item_actions|filter_admin_actions(item) %} | |
... lines 5 - 6 | |
{% endblock %} |
That filter does not exist yet: we're about to create it.
Just to review, open up the original list.html.twig
. The _list_item_actions
variable is set up here:
... lines 1 - 94 | |
{% block main %} | |
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} | |
... lines 97 - 189 | |
{% endblock main %} | |
... lines 191 - 244 |
Later, the for
loop creates an item
variable...
... lines 1 - 94 | |
{% block main %} | |
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} | |
<div class="table-responsive"> | |
<table class="table"> | |
... lines 100 - 129 | |
<tbody> | |
{% block table_body %} | |
{% for item in paginator.currentPageResults %} | |
... lines 133 - 164 | |
{% endfor %} | |
{% endblock table_body %} | |
</tbody> | |
</table> | |
</div> | |
... lines 170 - 189 | |
{% endblock main %} | |
... lines 191 - 244 |
which we have access to in the item_actions
block:
... lines 1 - 94 | |
{% block main %} | |
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} | |
<div class="table-responsive"> | |
<table class="table"> | |
... lines 100 - 129 | |
<tbody> | |
{% block table_body %} | |
{% for item in paginator.currentPageResults %} | |
... line 133 | |
<tr data-id="{{ _item_id }}"> | |
... lines 135 - 143 | |
{% if _list_item_actions|length > 0 %} | |
... line 145 | |
<td data-label="{{ _column_label }}" class="actions"> | |
{% block item_actions %} | |
{{ include('@EasyAdmin/default/includes/_actions.html.twig', { | |
actions: _list_item_actions, | |
request_parameters: _request_parameters, | |
translation_domain: _entity_config.translation_domain, | |
trans_parameters: _trans_parameters, | |
item_id: _item_id | |
}, with_context = false) }} | |
{% endblock item_actions %} | |
</td> | |
{% endif %} | |
</tr> | |
... lines 159 - 164 | |
{% endfor %} | |
{% endblock table_body %} | |
</tbody> | |
</table> | |
</div> | |
... lines 170 - 189 | |
{% endblock main %} | |
... lines 191 - 244 |
Phew! All we need to do now is create that filter! In src/AppBundle/Twig
, create a new PHP class: EasyAdminExtension
. To make this a Twig extension, extend \Twig_Extension
:
... lines 1 - 2 | |
namespace AppBundle\Twig; | |
... lines 4 - 6 | |
class EasyAdminExtension extends \Twig_Extension | |
{ | |
... lines 9 - 26 | |
} |
Then, go to the Code
->Generate
menu - or Command
+N
on a Mac - and override the getFilters()
method:
... lines 1 - 2 | |
namespace AppBundle\Twig; | |
... lines 4 - 6 | |
class EasyAdminExtension extends \Twig_Extension | |
{ | |
public function getFilters() | |
{ | |
... lines 11 - 16 | |
} | |
... lines 18 - 26 | |
} |
Here, return an array with the filter we need: new \Twig_SimpleFilter('filter_admin_actions',
[$this, 'filterActions']):
... lines 1 - 2 | |
namespace AppBundle\Twig; | |
... lines 4 - 6 | |
class EasyAdminExtension extends \Twig_Extension | |
{ | |
public function getFilters() | |
{ | |
return [ | |
new \Twig_SimpleFilter( | |
'filter_admin_actions', | |
[$this, 'filterActions'] | |
) | |
]; | |
} | |
... lines 18 - 26 | |
} |
Down below, create public function filterActions()
with two arguments. First, it will be passed an $itemActions
array - that's the _list_item_actions
variable. And second, $item
: whatever entity is being listed at that moment:
... lines 1 - 6 | |
class EasyAdminExtension extends \Twig_Extension | |
{ | |
... lines 9 - 18 | |
public function filterActions(array $itemActions, $item) | |
{ | |
... lines 21 - 25 | |
} | |
} |
Ok, let's fill in the logic: if $item instanceof Genus && $item->getIsPublished()
, then unset($itemActions['delete'])
. At the bottom, return $itemActions
:
... lines 1 - 4 | |
use AppBundle\Entity\Genus; | |
class EasyAdminExtension extends \Twig_Extension | |
{ | |
... lines 9 - 18 | |
public function filterActions(array $itemActions, $item) | |
{ | |
if ($item instanceof Genus && $item->getIsPublished()) { | |
unset($itemActions['delete']); | |
} | |
return $itemActions; | |
} | |
} |
Phew! That should do it! This project uses the new Symfony 3.3 autowiring, auto-registration and autoconfigure services.yml
goodness:
... lines 1 - 5 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
autowire: true | |
autoconfigure: true | |
... lines 11 - 12 | |
AppBundle\: | |
resource: '../../src/AppBundle/*' | |
exclude: '../../src/AppBundle/{Entity,Repository,Tests}' | |
... lines 16 - 32 |
So... we don't need to configure anything: EasyAdminExtension
will automatically be registered as a service and tagged with twig.extension
. In other words... it should just work.
Let's go. Refresh... and hold your breath.
Haha, it kind of worked! Delete is gone... but so is everything else. And you may have noticed why. We did change the _list_item_actions
variable... but we forgot to call the parent block. Add {{ parent() }}
:
... lines 1 - 2 | |
{% block item_actions %} | |
{% set _list_item_actions = _list_item_actions|filter_admin_actions(item) %} | |
{{ parent() }} | |
{% endblock %} |
Try it again. Got it! The delete icon is only there when the item is not published. This was a tricky example... which is why we did it! But usually, customizing things is easier. Technically, the user could still go directly to the URL to delete the Genus
, but we'll see how to close that down later.
Hey Diaconescu
I'm not sure if that's related to Symfony5 or to your local machine configuration. Do you have x-debug installed? If you open up the debug web toolbar, there is a section called "PHP Configuration" check if everything is configured
Cheers!
Hello,
I have followed the example to unset a list action which work great on some of my lists but one of my lists is using the dropdown for it's lists actions. Is there a way to unset a list action on a dropdown?
Thank you in advance!
I figured out why the action was not getting removed from the dropdown. I was using a different list template where I did not have my filter so it was showing up. Sorry, I wasted a post.
Hey Brent
Don't worry about it. Disqus can store some extra bytes ;)
btw, EasyAdminBundle may be confusing to override some times
Hello,
templates/admin/list.html.twig
`{% extends '@EasyAdmin/default/list.html.twig' %}
{% block item_actions %}
{% set _list_item_actions = _list_item_actions|filter_admin_actions(item) %}
{{ dump(item_actions) }}
{{ parent() }}
{% endblock %}`
I don't see the dump.
Looks like the file hasn't been overrided.
the file must be placed in the template folder ? in an easy_admin folder?
thanks
Hey ojtouch
Yeah template overriding is a tricky thing, to override bundle templates, you should place it in:templates/bundles/EasyAdminBundle/default/list.html.twig
and your extends string will be like:{% extends '@!EasyAdmin/default/list.html.twig' %}
Then it will work as expected.
Cheers!
And if I add this on easy_admin.yaml file :
`entities:
# List the entity class name you want to manage
User:
class: App\Entity\User
label: Utilisateurs
templates:
list: 'easy_admin/list.html.twig'`
I have this error :Variable "item_actions" does not exist.<br />
Hello guys! How to know name of directory (f.e. easy_admin) which EasyAdminBundle will automatically to use instead of default?
Yo Billy Van!
This is a special feature of EasyAdminBundle. In the previous chapter, I quickly mention that in their docs, they talk about all the different options for overriding templates: https://symfony.com/doc/cur.... One of them is to create this easy_admin directory: the bundle is programmed to look there.
Cheers!
I’m getting the following error:
Unknown "filter_admin_actions" filter in easy_admin/list.html.twig at line 4.
I’ve created the file EasyAdminExtension.php, with the functions:
Also i’ve created the new folder easy_admin (app/resources/views/easy_admin) with the list.html.twig
List.html.twig looks like:
{% extends '@EasyAdmin/default/list.html.twig' %}
{% block item_actions %}
{% set _list_item_actions = _list_item_actions|filter_admin_actions(item) %}
{% endblock item_actions %}
PHPStorm also says cannot found declaration for @EasyAdmin/def……….
When I compare my services.yml with the one from the course finish, it looks difference.
My service.yml looks like:
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
app.markdown_extension:
class: AppBundle\Twig\MarkdownExtension
tags:
- { name: twig.extension }
#arguments: ['@app.markdown_transformer']
autowire: true
app.security.login_form_authenticator:
class: AppBundle\Security\LoginFormAuthenticator
autowire: true
app.doctrine.hash_password_listener:
class: AppBundle\Doctrine\HashPasswordListener
autowire: true
tags:
- { name: doctrine.event_subscriber }
app.form.help_form_extension:
class: AppBundle\Form\TypeExtension\HelpFormExtension
autowire: true
tags:
- { name: form.type_extension, extended_type: 'Symfony\Component\Form\Extension\Core\Type\FormType' }
When I change my services.yml I get a new error:
A "tags" entry must be an array for service "AppBundle\Controller\" in /home/vagrant/documents/development/aqua_note/app/config/services.yml. Check your YAML syntax in /home/vagrant/documents/development/aqua_note/app/config/services.yml (which is being imported from "/home/vagrant/documents/development/aqua_note/app/config/config.yml").
How could I solve this issue?
Hey Bertin,
First of all, I see you have different indentation styles in one file, you use 4 spaces for it, but for the last service "app.form.help_form_extension" you use only 2 spaces. That could be a problem, first of all try to stick to a one style, I'd say it's easy to tweak the last service to use 4 spaces too for all:
services:
# other services here...
app.doctrine.hash_password_listener:
class: AppBundle\Doctrine\HashPasswordListener
autowire: true
tags:
- { name: doctrine.event_subscriber }
app.form.help_form_extension:
class: AppBundle\Form\TypeExtension\HelpFormExtension
autowire: true
tags:
- { name: form.type_extension, extended_type: 'Symfony\Component\Form\Extension\Core\Type\FormType' }
Also, in the error message I see we're talking about "AppBundle\Controller\" service, but I don't see it in your output. Did you show the content of proper file? Could you show the content of "/home/vagrant/documents/development/aqua_note/app/config/services.yml"? Because the problem should be in it.
Cheers!
I've changed the indent to 4 spaces. Also my previous comment has the services.yml. But i will post it again.
<strong>services.xml (/home/vagrant/documents/development/aqua_note/app/config/services.yml)</strong>
# Learn more about services, parameters and containers at
parameters:
# parameter_name: value
services:
app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
app.markdown_extension:
class: AppBundle\Twig\MarkdownExtension
tags:
- { name: twig.extension }
#arguments: ['@app.markdown_transformer']
autowire: true
app.security.login_form_authenticator:
class: AppBundle\Security\LoginFormAuthenticator
autowire: true
app.doctrine.hash_password_listener:
class: AppBundle\Doctrine\HashPasswordListener
autowire: true
tags:
- { name: doctrine.event_subscriber }
app.form.help_form_extension:
class: AppBundle\Form\TypeExtension\HelpFormExtension
autowire: true
tags:
- { name: form.type_extension, extended_type: 'Symfony\Component\Form\Extension\Core\Type\FormType' }
But still getting the following error:
" Unknown "filter_admin_actions" filter in easy_admin/list.html.twig at line 4."
And if i take a look at file: easy_admin/list.html.twig it looks like:
{% extends '@EasyAdmin/default/list.html.twig' %}
{% block item_actions %}
{% set _list_item_actions = _list_item_actions|filter_admin_actions(item) %}
{% endblock item_actions %}
It look likes the service is not enabled/configured but in the video they are mention it should
work because of autowiring / autoregistration / autoconfigure services.
Also mentioned before that my services.yml is different with the one from the start project directory.
The services.yml from the start directory looks like:
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
AppBundle\Service\MarkdownTransformer:
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
AppBundle\Doctrine\HashPasswordListener:
tags:
- { name: doctrine.event_subscriber }
AppBundle\Form\TypeExtension\HelpFormExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
But when is use the services.yml from the start directory the following error accours
A "tags" entry must be an array for service "AppBundle\Controller\" in /home/vagrant/documents/development/aqua_note/app/config/services.yml. Check your YAML syntax in /home/vagrant/documents/development/aqua_note/app/config/services.yml (which is being imported from "/home/vagrant/documents/development/aqua_note/app/config/config.yml").
So i miss something but couldn't find out what
Hey Bertin,
OK, so first of all the root of the problem is that you don't have the new service auto-registration. This part is necessary for it:
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
So it should be at the top of your services.yml if you want to have service auto-registration. Let's add it to your services.yml. Do you still have the error about?
A "tags" entry must be an array for service "AppBundle\Controller\" in ...
Please, try again and make sure you don't miss any brackets, etc. and your syntax is a valid YAML.
Btw, what version of Symfony do you use? Because this feature is new and does not work for Symfony version less than v3.3, see related discussion here: https://github.com/symfony/symfony-docs/issues/7988 . So the only way to use it is upgrade Symfony to 3.3 at least.
Cheers!
It's working again. It was a version problem. I used Symfony 3.1. After updating it with composer update its working. Thx for the help.
Hello guys,
You set up in twig "_list_item_actions|filter_admin_actions(item)" with one argument 'item'.
But in Twig extension we have public function filterActions(array $itemActions, $item) with two arguments. How twig knows about $itemActions?
Am I missing something?
Yo Axa!
Good question! :) You're totally right, filterActions() requires 2 args, but you need to know how Twig filters work. The 1st argument to the Twig filter *is always* a value which is on the left of the pipe "|" char. In your case, the first argument to filterActions() is the value of "_list_item_actions" variable, because it's standing right before | filter_admin_actions(). So the *second* argument to filterActions() is always the *first* argument in Twig filer parentheses, i.e. "item" variable in "|filter_admin_actions(item)". Does it clear for you now? Also, you can read official docs about Twig filter: https://twig.symfony.com/do...
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.3.*", // v3.3.18
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.17.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.40
"nelmio/alice": "^2.1", // v2.3.5
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
I try to rework this course in Symfony5. But any dump in easy_admin templates break the app with this error 'Uncaught PHP Exception Symfony\Component\ErrorHandler\Error\OutOfMemoryError: "Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65028096 bytes)" at /home/petrero/www/EasyAdmin/vendor/twig/twig/src/Extension/DebugExtension.php line 57'
I have a public repository at https://github.com/petre-sy... where is the entire app and is described the entire workflow in many commits. May be downloaded from there. What happen and how to remedy this?