This past week/weekend, I went through the process of defining a D8 content entity type with bundles (because I'm working on the d8 update of my programming with Drupal book), and with a UI that allows you to do CRUD on both the entity objects and bundles.
Keep in mind that I'm kind of an outsider to entity defining in D8 -- I haven't been very involved in building the existing core system (which I think gradually evolved) or the existing core entities.
I used the existing entities as a model... but it took me hours of debugging to get it all working, and the code and routes I ended up with were still not all that great at the end of it all. Maybe there were things I didn't understand (where we need better documentation), and maybe it's just that the DX needs improvement?
Anyway, defining a UI for entities seems very complex. Between the main entity and the config entity for the bundles, this involved defining:
- 8 routes with placeholders
- Various menu links, action links, and task links
- A theme hook, Twig template, and preprocess function for displaying the content entity
- 9 classes and interfaces for the entities, their forms, and the config list controller
- A config schema for the bundle entity
- A few methods in a Controller outside the entity space
- A URI callback function[no longer necessary]- A permission in hook_permission()
- Views integration for the content entity, assuming you want to use Views to make an admin page similar to admin/content (I didn't actually yet even do that); then you'd want to also export your view as config.
- And I just found out that if I am using SQL storage and want it to have decent indexes, I also need to define a storage controller class; see #2326949: Move entity-type-specific schema information from the storage class to a schema handler
This process seems overly complicated, and part of the problem was that I pretty much had to do all of the above steps before I could test anything in the UI.
Here are some things that occurred to me during this process, that I thought could be improved:
- [A] I am not sure why I needed to build an editing form for the config entity, when I think it could have been built from the config schema. Could we have a standard entity form class that we could use that would do that?
[Berdir: "Sure, that would be great" [just hasn't been done yet]
- [B] I am not sure why I needed to define an editing form for the content entity, when all it does is put up a field for the title/label, which should be pretty obvious from some other methods on the entity class etc. Could we have a standard entity form class that would do that?
[Berdir: (paraphrasing) you can use widgets for the form elements... see example links in comment #2]
Related issues:
#2312133: Make EntityForm::save() save the entity
#2230637: Create a Language field widget and the related formatter
#2226493: Apply formatters and widgets to Node base fields - [C] The delete confirm forms... For the config entity, that maybe had to be custom, because you have to figure out what to do if the bundle is deleted and there are entities defined in that bundle. But I don't see why the main entity delete needed its own custom form. Could we have a standard one?
[Berdir, paraphrasing: Existing entity types have small differences in wording, so it's hard to standardize.]
- [SORRY, there is no (d)]
- [E] Routes and menu links... These could be made totally standard, given a path prefix. For instance if I said the prefix was "myentity" for the content entity, then standard routes could be made for myentity/[ID]/[op], where [op] is delete or edit or missing for the view page. For the config entity, you just need edit/delete, even easier.
[Berdir, paraphrasing: Existing entity types have small differences, so it's hard to standardize.]
[timplunkett, paraphrasing: Making these automatic would make it harder to understand. Right now at least all the routes for a module can be found in one place.]
[jhodgdon: Maybe it could be made easier in the routing.yml files, something like:
my.route.name: _entity_route: myentity _route_type: edit
]
Related issues:
- #2259445: Entity Resource unification
- #2285413: [Meta] Standardize entity route names
- #2089757: Auto generate routing entries for entities based on an 'admin_path' and 'admin_permission' annotation
- #2316949: Title setting in EntityViewController::view() is not working right - [F] Theming - for the content entity, really I don't know why I had to define an entity theme for my entity type, Couldn't there be a fallback? All it does is maybe put up the title and let Field module do the rest. Couldn't that be standardized?
[Berdir, paraphrasing: Existing entity types have small differences, so it's hard to standardize.]
Related issues:
- #2301245: Entity system invokes non-existing theme hooks: "Theme hook $entity_type_id not found."
- #2270883: Automatically add theme hook suggestions for all entity types
- #2023571: Support preprocessing in EntityViewBuilder - [G] Config entity list builder - I think this could be standardized?
[Berdir, paraphrasing: Existing entity types have small differences, so it's hard to standardize.]
- [H] Views integration -- I didn't actually do this yet but it seems like that could probably be mostly standardized too?
This is actually being done on:
- #1740492: Implement a default entity views data handler - [I] Storage -- it seems like specification of NOT NULL for indexing efficiency could be done as part of the entity class's baseFieldDefinition() method? And maybe specification of what fields need indexes could be also on the base entity class? See #2326949-10: Move entity-type-specific schema information from the storage class to a schema handler.
Comments
Comment #1
jhodgdonComment #2
BerdirSome first answers.
a) Sure, that would be great. Config schema is fairly new. config translation UI's afaik actually use it to some degree. Problem is simply that nobody wrote this, like many other things.
b) Not sure what you exactly did in your content entity form, but you can use widgets for the form elements there, not for everything yet but we are working on this. See https://github.com/christophegalli/content_entity_example/blob/cee_devel... for an example and https://github.com/christophegalli/content_entity_example/blob/cee_devel.... There is an issue to have a default for save() too: #2312133: Make EntityForm::save() save the entity
For the language thing, there's also an issue to make that a widget: #2230637: Create a Language field widget and the related formatter. Was attempted in #2226493: Apply formatters and widgets to Node base fields but it is tricky because while it is very similar, it is slightly different in many places, see that issue.
c) Yes, I've been wondering about a default delete confirm form too. Problem as with some of your other points is that in core, those forms very often slightly vary in their behavior and language (writing generic, good confirmation messages is harder than you'd think, starts with lower/uppercase/plural labels and wording), sometimes for good, sometimes for no reason.
d... (no d) e) Yes, the other issue you found from Crell is about this. Again, if you look at core, most entity types have small differences, partially for historical reasons. Generating something by default would make it harder to apply those small variations, because they would have to be route alterations. Another point that tim mentioned is that it will possibly be harder to look at an existing module and understand what it does. Right now, the routing.yml is basically the summary of all the UI's that a module provides. I *think* that I would prefer a one-time genreation of all the needed routes with comments/explanations over magic at this point.
f) Yes. I've been working on standardizing things like that for a while. Related issues:
- #2023571: Support preprocessing in EntityViewBuilder
- #2301245: Entity system invokes non-existing theme hooks: "Theme hook $entity_type_id not found."
- #2270883: Automatically add theme hook suggestions for all entity types
Again, one problem was slight differences in the existing templates (example, node template used node_url, others used url and so on. This example actually got fixed).
Not sure if you ever did this in 7.x (without entity.module, this is an area where it still has some improvements on 8.x), but it was not any better there.
g) Again, same (boring) story, reality is that there are almost always small differences in the dozen or so config list builders in core, AFAIK it did include the label at some point, but the result was that there were more subclasses that had to alter it (which is actually more complicated code) than those that could just use it. And yet another subclass wouldn't be nice either, but was discussed before.
Comment #3
jhodgdonRegarding things being slightly different between entity to entity on some of the classes... I do realize that.... But at least if the base classes had more methods on them so that they weren't abstract, and defined some default behavior, they would function without the need to subclass. Then you could at least get going with minimal work and refine/subclass later on if necessary.
As it is now, I really couldn't even test *anything* in my entity definition module or even enable it until all of the classes, routes, etc. that I listed in the issue summary had been created, and then I had to debug everything (all very interconnected) at once, which was quite a lot of code and I had a lot of bugs due to there being a lot of stuff I didn't know and that wasn't obvious, even after looking at the examples. I mean, you want to make a minimal working set and then build on it, and it's not clear looking at the examples what is essential for my entity vs. specific stuff for those specific entities, and saying you need to look at all the examples at once and compare and contrast (which is basically what I ended up having to do) is ... a lot of work. We're talking all those classes for all those entities, and for most of them, the classes are scattered around between Entity, Form, Controller, and the base module namespace, not even collected all in one place, making it even harder to figure out what is going on... Really it's a huge mess and the examples in core are rather inscrutable.
Regarding the routes... Maybe we could somehow have a shorthand that would get all the permissions, placeholders, etc. right? Something like:
my.route.name:
_entity_route: myentity
_route_type: edit
Because I *still* don't know how to get the routes working right. I ended up not even using the entity permissions in the requirements section of the routes, just a generic "administer my entity" permission instead, because I couldn't get it working immediately and wanted to move on to debugging other things. Way more painful than it needed to be... I am still not sure of the interaction between entity loading, entity classes, entity annotation, permissions, and routes... but they're all interconnected in ways that are not obvious to me, even now.
Oh and no, in 7.x I always used the Entity API module. It was certainly a mess without in 7.x as well. I'm not saying necessarily that it's worse, but in 7.x there wasn't the distinction between config and content entities so at least you only had to learn about one way to do things. In 8.x, what was one convoluted section in the 7.x version of the book (even with Entity API) is now two convoluted sections of about the same length as the 8.x section. It just doesn't seem to be better.
Comment #4
jhodgdontimplunkett commented in IRC:
I just want to say that each individual class wasn't really a problem. It was just the sheer number of classes, routes, etc. that needed to be defined, most of which had to be all created at once, with no typos, before anything could be tested, that made the whole process tedious.
That and a few, as far as I can tell, undocumented gotchas. And the routing stuff for entities is also a bit opaque...
Anyway, we don't have to fix this... they are just suggestions that I think could make the process easier to do by increments. As webchick said to me in IRC yesterday, the thought was that the plugin/entity system in D8 was supposed to make things easier. I think it has for generic plugins, like blocks, which are all on one class... but I don't see that the process is any easier for entities than it was in D7. It is still rather convoluted and involved -- as noted in the issue summary, you have to edit or create at least 16 different files, in order to define 1 content entity type with bundles. [I just added one: I had forgotten about the config entity schema.]
Comment #5
jhodgdonOh, and I didn't even get to the Views integration, which I would need/want in order to make the equivalent of admin/content for this entity.
Comment #6
jhodgdonTim pointed out to me in IRC that the craziness here is only true if you want a UI. If you do not need a UI, then you can just define your two entity classes and you are done.
I'm updating the issue summary.
Comment #7
catchViews integration: #1740492: Implement a default entity views data handler.
Comment #8
xjmComment #9
jhodgdonAdded related issues and issue comments to Related Issues and the issue summary.
Comment #10
jhodgdonBerdir helped me figure out where I went wrong in a couple of places, and things are *slightly* simpler. Updating summary and adding new related issue.
Comment #11
alexweber CreditAttribution: alexweber commentedJust stumbled upon this... as someone who's been watching from a distance and playing with the new APIs every couple weeks or so I gotta echo @jhodgdon here on this one!
I'm very familiar with D7 Entity API and pretty familiar with all of the D8 plugins/service architecture and I gotta say that I've spent multiple hours at a time on multiple occasions referring to core example and docs and trying to get some sample content entities going and boy, is it an adventure!
It's actually super elegant once you wrap your head around it but the end result is kinda like D7 where you have piles of boilerplate model entity code that you are copying and pasting and renaming for each new project kinda deal, which just sucks.
All of that said, I think scaffolding will make none of this matter anymore: https://www.drupal.org/project/console :)
Comment #12
effulgentsia CreditAttribution: effulgentsia commentedDo folks here want to share any thoughts about #2326949: Move entity-type-specific schema information from the storage class to a schema handler (which proposes to add yet one more class) on that issue? Thanks.
Comment #13
jhodgdonGracious. Commented on the other issue, and adding Storage to the summary. Also reformatted the summary for easier scanning.
Comment #14
jhodgdontypo
Comment #15
andyceo CreditAttribution: andyceo commentedComment #16
xjmThis issue was marked as a beta target for the 8.0.x beta, but is not applicable as an 8.1.x beta target, so untagging.
This could potentially be implemented in a minor with BC, so moving to 8.2.x.
Comment #17
bojanz CreditAttribution: bojanz commentedImprovement made for 8.1:
#2596095: Provide a route provider for add-form of entities
And one that's RTBC for 8.1:
#2666792: Provide a route provider for add-page of entities
Comment #22
joachim CreditAttribution: joachim as a volunteer commentedI'd like to look at reducing the amount of boilerplate for custom entity types -- is this the best issue to focus things on, or should I start a new Plan issue, since I think this isn't necessarily about providing a UI?
Now I've done the bulk of custom entity type generation in Module Builder, I think I have a pretty good idea of some areas where things could be improved.
Collecting some related issues:
- #2809177: Introduce entity permission providers
- #2949964: Add an EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface
- #1798540: Add link to add a new entity in an empty entity list controller table
- #2976861: add an entity Links handler, to provide menu links/tasks/actions for entity types
Comment #25
AaronMcHaleClosing in favour of #2095603: [meta] Complete Entity Field API as it is tracking more issues and has the same purpose.