Right now, you need to list entity class names you want to be managed by the admin in a configuration file (under config/packages/easy_admin.yaml in Symfony 4 and app/config/config.yml in Symfony < 4).
That's not really convenient as we need to remember to sync this config file in case of a rename. And whenever a new entity is created, it means two hops before being done.
I propose to use another way (in addition to the current way to keep BC) to configure entities, via an annotation (@AdminResource for instance) that one would use to tag entity classes:
/**
* @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
* @AdminResource
*/
class Product
{
}
Auto-discovery could be done via a simple configuration pointing to the Entity directory (which would be configured by default in the Flex recipe):
easy_admin:
resource: '%kernel.project_dir%/src/Entity'
I've used resource here to be consistent with other parts of the framework, but we could use something else.
That way, everything is contained in the Entity class (like for the ORM, ApiPlatform configuration).
WDTY?
I think it's quite rare to not modify the default configuration EasyAdmin creates for you. Unfortunately, as soon as you tweak the configuration the usefulness of this annotation goes out the window.
That is unless you allow passing arbitrary entity configuration into the annotation:
/**
* @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
* @AdminResource({
* "list"={
* "fields"={
* "title",
* ...
* }
* }
* })
*/
class Product
{
}
@iluuu1994 what about configure fields through field annotation, instead of class annotation?
I'm not a fan of configuration by annotations. Those things, in my opinion, should be stored in separate config file(s).
@ktrzos annotation in symfony is one way where you could put configuration. This is your choice where you want to store it: yml, xml, php or annotations.
@grachevko Yeah sure. However I think this makes things overly complicated. For example:
I'm sure these issues could be solved in some way or another. However keeping the entire config in the class annotation would allow us to just merge it just like every other config in EasyAdmin. This would cause the least confusion and require very little work.
Indeed, it's tempting to have everything about entity class in one file. But in current implementation it is possible to configure many EA menu entries out of one entity class:
easy_admin:
entities:
One:
class: AppBundle/Entity/One
Two:
class: AppBundle/Entity/One
Would it be possible using annotations? I actually use this trick to create different views for a City entity - one is standard form, second is map widget view.
@forsetius Sure, I'm pretty positive that the Doctrine annotation parser allows adding the same annotation twice to a class (haven't tested it though).
Yeah, sure but we do it so that we have different sets of fields in each EA entry. So, in such case an entity's field needs to have annotation like:
/**
* @AdminField (adminEntry="One", <rest of config for One here>)
* @AdminField (adminEntry="Two", <rest of config for Two here>)
*/
Remember that to properly configure the field, we need to configure the label (needed for translation), action views (list, show etc.) and for each action: CSS classes and sometimes type and type options. In some cases label needs to be different for different actions.
I'm worrying that resulting annotations for each field would be too convoluted if we'd have to define them separately for each EA entry :(
@forsetius Yeah, sorry. I didn't get that. You'd need an optional annotation property for configuring the alias of the configured entity.
I'm worrying that resulting annotations for each field would be too convoluted if we'd have to define them separately for each EA entry :(
Not sure what you mean by that. That's what you have to do right now in YAML as well, don't you? Also, EasyAdmin still infers a whole lot of things for you.
Can i suggest also define convention about annotation aliases?
By analogy with:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
Use something like this
use EasyCorp\Bundle\EasyAdminBundle\Mapping\Annotation as EA;
/**
* @EA\Entity
*
* @ORM\Entity
*/
class Product
{
/*
* @var int
*
* @EA\Property
*
* @ORM\Column
*/
private $id;
/*
* @EA\Property
*/
public function virtualField(): string;
}
IIRC one of the things on Javier's list is too refactor how entities are configured (at least the internal code for that). Supporting annotations (and maybe even other config formats) could then be taken into account when designing the new internal API.
@grachevko
A few examples why this isn't a good idea:
virtualField before id? (Please don't say change the order of the properties 馃槀)@EA\Property annotation on the getter and not the setter?IMHO this just feels very inconsistent and overly complicated. Additionally there's just a lot of things that couldn't be configured this way.
@grachevko: I gave you +1 because I like the annotation before virtualField. Nice and concise way of stating it's meant for display in EA only.
@iluuu1994: oh, yeah, the order of elements. The solution would be: @EA\Property(order=1) or @EA\PropertyAction(name="form", order=2). As of convoluted: I'll try to prepare something more real-life for tomorrow... err, today.
Oh, just occured to me: since that annotation-type config is a part of PHP it can be used in traits. It has upsides (you define a config for a field once, no repetition like defining equivalent of: - {property: 'name', label: 'trait.name' } zillion times) and downsides (you defined a config for a field once and then you need to change only cssClass for one action in one entity). I'll provide aforementioned example to make the downside more clear.
Sorry for delay, here is more real-life example of current yaml config and my attempt to translate it to annotations. Assumptions:
@EA\Entity annotations to assign EA entities (menu entries) to Doctrine entities. Also, EA entity properties except actions and class defined here@EA\Action annotations to define action properties except fields. If more than one EA entities defined on the Doctrine entity and their properties differ on given action then entity attribute must be provided for that action@EA\Property annotations to define attributes common for all actions@EA\PropertyAction annotations to denote that field has to be actually rendered for given action. Attributes given here override @EA\Property ones. If more than one EA entities defined on the Doctrine entity and their properties differ on given action then entity attribute must be provided for that actionI tried to minimize the amount of text required (by attributes overrides) but as you'll see it comes at a cost of clarity. Here we go...
First current yaml config:
easy_admin:
entities:
City:
class: AppBundle\Entity\City
form:
widgets: ['color']
fields:
- { property: 'name', label: 'trait.name', type: text, css_class: col-sm-10 }
- { property: 'color', label: 'trait.color', css_class: col-sm-2, type: 'AppBundle\Form\Type\ColorType' }
-
property: 'zones'
label: 'zone.enames'
type: entity
type_options:
multiple: true
class: AppBundle\Entity\Zone
by_reference: false
- { property: 'extent', label: 'trait.extent', css_class: hidden}
new:
title: city.etitle.new
edit:
title: city.etitle.edit
show:
title: city.ename
fields:
- { property: 'name', label: 'trait.name', css_class: col-sm-4 }
- { property: 'color', label: 'trait.color', css_class: col-sm-2, template: '@App/widgets/color/field.html.twig' }
- { property: 'zones', label: 'zone.enames', css_class: col-sm-6 }
- { property: 'extent', label: 'trait.extent', type: raw, css_class: hidden }
list:
title: city.enames
fields:
- { property: 'coloredName', label: 'trait.name', template: '@App/widgets/color/field.html.twig' }
- { property: 'zones', label: 'zone.eicon' }
Map:
class: AppBundle\Entity\City
disabled_actions: ['list', 'new', 'edit']
show:
title: city.emap
fields:
- { property: 'name', label: 'trait.name' }
- { property: 'extent', label: 'trait.map', type: raw, template: '@MapWidget/map_field.html.twig'}
Now, attempt at annotations:
<?php
// snip "uses"
/**
* @ORM\Entity
* @ORM\Table(name="m_cities")
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
* @EA\Entity(name="City")
* @EA\Entity(name="Map", disabledActions="list,new,edit")
* @EA\Action(name="new", label="city.etitle.new")
* @EA\Action(name="edit", label="city.etitle.edit")
* @EA\Action(entity="City", name="show", label="city.ename")
* @EA\Action(name="list", label="city.enames")
* @EA\Action(entity="Map", name="show", label="city.emap")
*/
class City
{
use SoftDeleteableEntity;
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255, unique=true)
* @EA\Property(label="trait.name", order=1)
* @EA\PropertyAction(name="form", type="text", css_class="col-sm-10")
* @EA\PropertyAction(entity="City", name="show", css_class="col-sm-4")
* @EA\PropertyAction(entity="Map", name="show")
*/
protected $name;
/**
* @ORM\Column(type="string", length=7)
* @EA\Property(label="trait.color", order=2, css_class="col-sm-2")
* @EA\PropertyAction(name="form", type="AppBundle\Form\Type\ColorType")
* @EA\PropertyAction(entity="City", name="show", template="@App/widgets/color/field.html.twig")
*/
protected $color = '#ffffff';
/**
* @ORM\Column(type="string", length=255, nullable=false)
* @EA\Property(label="trait.extent", order=4, css_class="hidden")
* @EA\PropertyAction(name="form")
* @EA\PropertyAction(entity="City", name="show", type="raw")
* @EA\PropertyAction(entity="Map", name="show", type="raw", template="@MapWidget/map_field.html.twig", css_class="col-sm-12")
*/
protected $extent;
/**
* @ORM\OneToMany(targetEntity="Zone", mappedBy="city")
* @EA\Property(label="zone.enames", order=3)
* @EA\PropertyAction(name="form", type="entity", type_options={"multiple"=true, "class"="AppBundle\Entity\Zone", "by_reference"=false})
* @EA\PropertyAction(entity="City", name="show", css_class="col-sm-6")
* @EA\PropertyAction(name="list", label="zone.eicon")
*/
protected $zones;
/**
* Virtual property
* @EA\Property(label="trait.name", order=1, template="@App/widgets/color/field.html.twig")
* @EA\PropertyAction(name="list")
*/
public function coloredName()
{
return $this->name .'|color~'. $this->color;
}
// snip accessors
}
I don't know how to represent the widgets custom attribute using annotations.
As you can see yaml is much more concise, clear and orderly. The only annotation's advantage I can see is that it comes in one place with model's and Doctrine entity's definition. I'm not sure it's worth it.
I'm not sure it's worth it.
Me neither. Not only does the complexity increase quite a bit it is also inconsistent with the YAML configuration. Especially the mixing of different entities and actions makes it very unreadable IMHO.
The only annotation's advantage I can see is that it comes in one place with model's and Doctrine entity's definition.
But then again, we could always allow the user to pass the entity config in one piece into the @EA\Entity annotation. This way it's still in the same file.
Sadly I must close this feature as "won't fix". We no longer have resources to work on big and complex features like this. However, I've added it to our public roadmap in case some day we can work on this again. Thank you all for the discussion.
Most helpful comment
Can i suggest also define convention about annotation aliases?
By analogy with:
Use something like this