More with EasyAdminBundle
Posted on: 2015-06-20 | Categories:
PHP
A few weeks ago I’ve posted a beginner tutorial on how to start working with Symfony2 EasyAdminBundle admin panel which has been rewritten twice because of the newly introduced functionalities. Today I want to show you more: some advanced customization and integration with FOSUserBundle
and VichUploaderBundle
(and maybe more soon) which are often used.
Better configuration
For typical application it’s not convenient to store all bundles configuration in one (config.yml) file. For better organization we can move EasyAdminBundle
configuration (or any other bundle if you wish) to separate file (admin.yml
in this case) and than import it on the top of app/config/config.yml
file:
|
imports: - { resource: parameters.yml } - { resource: security.yml } - { resource: services.yml } - { resource: admin.yml } # <-- add this line |
You can view the changes in this commit.
Flip Switches
Another really nice feature are flip switches. Flip switches allow users to flip state of boolean
properties. For example if we have User
entity with boolean
property: enabled
or locked
, we can change its value and disable User
from the list
view. This is done by calling proper AJAX action by EasyAdminBundle
. No extra code is necessary to handle those actions. What is also worth noting we can modify it from standard edit
form view with checkbox widget.
Overriding templates
Usually default theme is not enough, we and our clients need some changes that we need to introduce to admin panel. EasyAdminBundle
starting from version 1.4
offers nice and developer friendly customization possibilities.
How to add Logout
button?
In this case if we want to modify template that is used by all EasyAdminBundle
views we should override layout file: layout.html.twig
. To do that we should create new easy_admin
folder in app/Resources/views
directory and copy / paste default EasyAdminBundle
layout to be able to modify and extend it.
|
<div id="header-footer" class="navbar-right"> {% block header_footer %} {% if app.user %} <div id="header-security"> <p> <small><i class="fa fa-lock"></i> <span>{{ 'header.logged_in_as'|trans }}</span></small> {{ app.user.username|default('Unnamed User') }} | <a href="{{ path('fos_user_security_logout') }}" title="logout">logout</a> </p> </div> {% endif %} {% endblock header_footer %} </div> |
Notice: In this example we have used FOSUserBundle
route: fos_user_security_logout
as we have integrated it earlier with our EasyAdminBundle
.
After those changes were introduced we need to reset Symfony2 cache and refresh the page to view the changes (logout
link is shown on images below).
|
# depending on system and tools php app/console cache:clear # or shortcut sf c:c |
Notice: If you are new to Symfony2 commands shortcuts and aliases, I encourage you to read this blog entry: Using Symfony2 console the right way.
This was really easy and small change in our layout but we have almost unlimited possibilities:
- any template or fragment of the backend can be overridden
- each template can be overridden globally or per entity
- we are not limited only to override templates – starting form
EasyAdminBundle
version 1.5
, we can override any action executed during the backend workflow.
FOSUserBundle integration
This is one of the most common integration as we always need some authentication and users to login to EasyAdminBundle
application. Installation has been described in previous entry.
Before we start integration we need to update User
entity and add two properties:
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
|
<?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use FOS\UserBundle\Model\User as BaseUser; use Doctrine\Common\Collections\ArrayCollection; use Gedmo\Mapping\Annotation as Gedmo; /** * User * * @ORM\Table() * @ORM\Entity */ class User extends BaseUser { // ... /** * @var \DateTime * * @Gedmo\Timestampable(on="update") * * @ORM\Column(name="updated_at", type="datetime") */ private $updatedAt; /** * @var \DateTime * * @Gedmo\Timestampable(on="create") * * @ORM\Column(name="created_at", type="datetime") */ private $createdAt; // ... } |
and after that generate setters and getters from shell:
|
# full version php app/console doctrine:generate:entities AppBundle:User # or shortcut sf d:g:entities AppBundle:User |
and update DB schema:
Those properties are needed to complete integration process with FOSUserBundle
: we need information about creation date and date of update. In the previous entry I have described how to configure basic users list and add / edit form.
To biggest problem with this integration is creating and updating users and saving / updating their passwords.
Latest version of FOSUserBundle
provides UserListener
which is updating canonical fields and password field on prePersist
and preUpdate
events. The thing is that we need to remember that configuring new or edit user forms we must embed plainPassword
attribute instead of password
. This will cause setting and updating password
property by UserListener
and its prePersist
and preUpdate
events listeners.
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
|
<?php class UserListener extends AbstractUserListener { public function getSubscribedEvents() { return array( Events::prePersist, Events::preUpdate, ); } /** * @param LifecycleEventArgs $args */ public function prePersist($args) { $object = $args->getEntity(); if ($object instanceof UserInterface) { $this->updateUserFields($object); } } /** * @param PreUpdateEventArgs $args */ public function preUpdate($args) { $object = $args->getEntity(); if ($object instanceof UserInterface) { $this->updateUserFields($object); // We are doing a update, so we must force Doctrine to update the // changeset in case we changed something above $em = $args->getEntityManager(); $uow = $em->getUnitOfWork(); $meta = $em->getClassMetadata(get_class($object)); $uow->recomputeSingleEntityChangeSet($meta, $object); } } } |
To display password field in User
add / edit form we need to configure it in app/config/admin.yml
file in form section – we can configure instead new
and edit
sections to provide different configuration for each form type.
|
easy_admin: entities: User: label: 'Users' class: AppBundle\Entity\User list: title: "User list" fields: ['id', 'username', 'email', 'lastLogin', 'enabled', 'locked'] actions: ['-show'] form: fields: - { property: 'username' } - { property: 'email', type: 'email' } - { property: 'plainPassword', type: 'password', label: 'Password', help: 'Passwords must have at least 8 characters' } - { property: 'enabled', type: 'checkbox' } - { property: 'locked', type: 'checkbox' } |
Notice: if we want to update for example only password attribute during one User
update, we need to implement special hook as plainPassword
field is not User
mapped property and updating it will not trigger UserListener
to update password field.
Update password hook
Now all we have to do is to update updatedAt
property each time plainPassword
property will be set.
|
public function setPlainPassword($password) { $this->plainPassword = $password; $this->updatedAt = new \DateTime(); return $this; } |
That’s it. We can now set or update User
password from EasyAdminBundle
panel.
Notice: We can also implement Doctrine timestamable
behavior which will set updatedAt
and createdAt
properties each time we change / create User
entity.
VichUploaderBundle integration
The VichUploaderBundle
is a Symfony2 bundle that attempts to ease file uploads that are attached to ORM entities, MongoDB ODM documents, PHPCR ODM documents or Propel models.
This bundle is really useful for uploading almost any kind of files. I’ve listed below some major features:
- automatically uploads files to a configured directory (mappings)
- loads the file back into the entity when it is loaded from database as an instance of
Symfony\Component\HttpFoundation\File\File
- deletes the file from the file system on removing entity from DB
In this example I’m going to add support for uploading customer logo image in EasyAdminBundle
panel.
Before we start we need to install VichUploaderBundle
bundle and setup necessary configuration. To install VichUploaderBundle follow the steps from documentation.
After that we need to configure VichUploader
to work with our Customer
entity but first we should add logo
property to Customer
entity.
Customer’s logo
To add logo
property to Customer
entity we need to update entity class and add the following code to src/AppBundle/entity/Customer.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
|
<?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Gedmo\Mapping\Annotation as Gedmo; use Symfony\Component\HttpFoundation\File\File; use Vich\UploaderBundle\Mapping\Annotation as Vich; /** * Customer * * @ORM\Table() * @ORM\Entity * * @Vich\Uploadable */ class Customer { // ... /** * @var string * * @ORM\Column(name="logo", type="string", length=255) */ private $logo; /** * @var \DateTime * * @Gedmo\Timestampable(on="update") * * @ORM\Column(name="updated_at", type="datetime") */ private $updatedAt; /** * @var \DateTime * * @Gedmo\Timestampable(on="create") * * @ORM\Column(name="created_at", type="datetime") */ private $createdAt; // ... } |
and as for User
entity we need to generate setters and getters for Customer
:
|
> php app/console doctrine:generate:entities AppBundle:Customer |
and update DB schema:
|
> php app/console doctrine:schema:update --force |
Our entity has now logo
property and we can setup logo uploading process.
Configure an upload mapping
First we are going to set mappings in app/config/config.yml
file. Those mappings are later used for uploading Customer
logo to proper location.
|
vich_uploader: db_driver: orm # or mongodb or propel or phpcr mappings: customer_image: uri_prefix: /images/customers upload_destination: %kernel.root_dir%/../web/images/customers |
Link the upload mapping to an entity
Now we need to make some kind of connection between the filesystem and Customer
entity. To do that we must add the following code to src/AppBundle/Entity/Customer.php
file:
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
|
<?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Gedmo\Mapping\Annotation as Gedmo; use Symfony\Component\HttpFoundation\File\File; use Vich\UploaderBundle\Mapping\Annotation as Vich; /** * Customer * * @ORM\Table() * @ORM\Entity * * @Vich\Uploadable */ class Customer { // ... /** * @Vich\UploadableField(mapping="customer_image", fileNameProperty="logo") * * @var File $logoFile */ protected $logoFile; // ... /** * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image */ public function setLogoFile(File $image = null) { $this->logoFile = $image; if ($image) { // It is required that at least one field changes if you are using doctrine // otherwise the event listeners won't be called and the file is lost $this->updatedAt = new \DateTime('now'); } } /** * @return File */ public function getLogoFile() { return $this->logoFile; } // ... } |
And that’s all. Now we will use EasyAdminBundle
syntax to add logoFile
field to Customer
form and after submit, the uploaded file will automatically be moved to the location we configured in app/config/config.yml
and the logo
property will be set to the filename of the uploaded file.
Note: I do not recommend uploading files / images the way it’s described in the official Symfony2 documentation. Just try both methods and make a choice.
EasyAdminBundle forms
To handle file upload in EasyAdminBundle
panel we need to add to Customer
form logoFile
field with proper configuration – this syntax has been described below:
|
easy_admin: entities: Customer: class: AppBundle\Entity\Customer form: fields: - user - name - { property: 'logoFile', type: 'file', label: 'Upload logo', help: 'Select file to upload / change logo' } - firstName - lastname - { property: 'email', type: 'email' } - phone - companyName - website |
As a result we should have form similar to this one:
Note: Another nice option is help
attribute that we cen set for each form field. Help
property is message displayed below the form field in the edit
, new
and show
views.
More…
We can also display this image on Customer’s list
and show
views. To do that we only need to set proper option in app/config/admin.yml
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
asy_admin: entities: Customer: class: AppBundle\Entity\Customer list: fields: - user - name - { property: 'logo', type: 'image', base_path: 'path/to/images' } - email - createdAt show: fields: - user - name - { property: 'logo', type: 'image', base_path: 'path/to/images' } - firstName - lastname - email - phone - companyName - website |
And the final result:
As always source code used in this examples can be downloaded / cloned from our Level7
GitHub repository.
Let me know if you have any problems with other EasyAdminBundle
integrations – I will try to solve it and describe here.
Good luck with your integrations!
Alex Rock Ancelet
June 21, 2015 12:21
This guide is simply awesome, and fits to latest EasyAdmin versions, great job !
vit
July 13, 2015 06:05
Really nice and easy. But what about embedded forms?
Kamil
July 13, 2015 08:01
Unfortunately, full support for embedding forms is still in progress: https://github.com/javiereguiluz/EasyAdminBundle/issues/124
Sjoerd Adema
September 17, 2015 12:57
This example is good, Vich and EasyAdminBundle make a great couple for quick administration that used files (images).
What I do not get working currently is one entity with a collection of images. Vich-Uploader has a great example of how this can be done (with Doctrine and SonataAdmin).
The logic for this is contained in this branch/folder, the MultiUploadableBundle in the sonata-example branch:
https://github.com/K-Phoen/Vich-Uploader-Sandbox/tree/doctrine-sonata/src/KPhoen/Bundle/MultipleUploadableBundle
It uses a child entity for the images (BikeImage with a manyToOne relation with Bike) but I don’t know if this structure is possible in the EasyAdminBundle… Anyone an idea?
vit
November 19, 2015 16:04
How to avoid redirection to list after form submission?
Thanks in advance!
Mickael
December 14, 2015 21:33
Hi,
We follow the VichUploaderBundle sample but update is not working. Any ideas ?
Regards,
Mickaël
Kamil
January 8, 2016 12:36
Could you please give me more details? What is not working? File is not being uploaded?
Cheers,
Kamil
Slayer
December 23, 2015 22:08
i’m relative new to Symfony, and i found this great tutorial … The example works … but the file is not beeing uploaded phisically, and the logo attibute in the database is not updated/inserted, can you help me please?? Thank you.
Kamil
January 8, 2016 12:35
Could you please give me more details? Some code? Github link?
Cheers,
Kamil
karel.barel
January 11, 2016 20:42
Thanks for the great tutorial.
nistlrooy
February 6, 2016 01:43
i used sonata admin bundle,but i use easy admin bundle now!
Luis
February 24, 2016 14:35
Thank you, really useful article. In particular, integration with VichUploaderBundle.
Manu
April 11, 2016 14:47
Thanks ! This is a great tutorial. But i have a problem with the file uploading. In fact, the folder and database cell are empty. I searched but i don’t find the solution.
Can you help me ?
Manu
April 11, 2016 15:41
I found the solution for the uploading, but i doesn’t see the images on edit view