Problem/Motivation
EntityManager started as just another plugin manager for entity types.
It was then expanded to include the following:
- Entity type discovery
- Helpers for loading entity types
- Entity type bundle discovery
- Entity display discovery
- Four different listener interfaces
- Entity definition management
- All entity field methods
Yet it has no methods related to managing entities! It handles entity types, entity type bundles, entity display objects, entity fields...
It's now 1451 lines with 49 public methods, 12 protected methods, and 9 service dependencies.
Proposed resolution
Split each of the ten distinct parts of EntityManager into a separate service
Maintain full BC for the existing code
Remaining tasks
Determine where getTranslationFromContext() should live
Finish documentation
User interface changes
N/A
API changes
N/A
Beta phase evaluation
| Issue category | Task because nothing is broken, just brittle and confusing |
|---|---|
| Issue priority | Major because we have the opportunity to drastically decouple the various parts of the entity system, greatly increasing learnability, testability, discoverability, and extensibility. Additionally, all of the distinct classes must use the public APIs of the other classes, ensuring that the entity system can be extended and used in complex use cases. |
| Disruption | Zero disruption because there is 100% backwards compatibility |
RC Target justification
See above beta evaluation.
While this is non-BC-breaking, in order for modules to be able to utilize these changes, it is important for it to be in before 8.0.0. If this were to wait for 8.1.x, modules using the new interfaces would be incompatible with 8.0.x for that entire duration.
| Comment | File | Size | Author |
|---|---|---|---|
| #78 | 2337191-entity_manager-78.patch | 339.25 KB | tim.plunkett |
| #78 | interdiff.txt | 6.11 KB | tim.plunkett |
| #76 | 2337191-entity_manager-76.patch | 339.04 KB | tim.plunkett |
| #71 | 2337191-entity_manager-71.patch | 339.6 KB | tim.plunkett |
| #71 | interdiff.txt | 33.53 KB | tim.plunkett |
Comments
Comment #1
yched commentedA service that handles fields on content entities... That sounds like going back to FieldInfo :-) (well, a richer version)
Not for or against atm, just saying.
Comment #2
xjmThere are parts of this that are internal-only, but some parts of this likely affect the public Entity API and should be beta deadline.
Comment #3
tim.plunkettDuring this issue we could even consider fixing the naming problem and renaming this EntityTypeManager, since that is what it does, manage entity types.
Comment #4
catchIf this misses the beta deadline, presumably we could still create the new service and have the old methods just call it?
Comment #5
rosinegrean commentedHey, should I start to work on this ?
Comment #6
dawehner@prics
This would be cool. Maybe its worth to try to write it in a BC compatible way but I guess this API is not something every module would use ...
Comment #7
berdirActually, it is a pretty major API (change), so yes, I think we should keep the old functions around. We really can't just move on as we did before beta...
Comment #8
yched commentedRelated : #2472013: Move view_mode / display handilng code out of EntityManager and into a new EntityDisplayManager
Fun facts copied from there :
:-)
Comment #9
tim.plunkettEntityManager has grown even more since I opened this issue, now containing:
Each of these should be a separate service.
This patch builds on #2544746: Rewrite EntityManagerTest to use phpspec/prophecy, since only that helped get the tests to a decoupleable state. (The do-not-test patch assumes it gets committed).
Interestingly enough, all of the parts of EntityManager that I had trouble testing cleanly with prophecy went away in this refactoring.
This patch maintains full BC, with no API breaks.
This patch moves 1-4 out to new interfaces. I'm not sure if each set of listeners should get their own or not, and I ran out of steam before moving the field stuff.
When this is ready to commit, EntityManager will have no real methods of its own, only wrappers for BC.
This is just a WIP, and I will be working on it more (after hours, since it is very much not a critical).
Comment #10
dawehner.
Comment #11
tim.plunkettFinished moving out the listeners and field stuff. Only getTranslationFromContext() is left, where should that go?
Also the test issue went in, yay.
Comment #12
tim.plunkettThis is pretty much the only real change I had to make, since the listeners were using $this->fieldMap directly since it happened to be on the same class.
The rest I kept as close to copy/paste as possible.
Adding a couple @todos because all of the non-plugin related code was relying on
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();being called fromdrupal_flush_all_caches()We need a more generic mechanism for resetting these static caches.
Comment #13
tim.plunkettComment #14
jibranThis all makes ton of sense for me. Naming also looks good. Here are few remarks I discussed in IRC with @tim.plunkett.
getTranslationFromContexthas to move it has nothing to do with EntityManager. @tim.plunkett will ping @plach @GaborHojtsy about it.EntityTypeRepository::loadEntityByUuid()&EntityTypeRepository::loadEntityByConfigTarget()seems out of place.getTranslationFromContext,loadEntityByUuid, andloadEntityByConfigTargetall returns entity so @tim.plunkett thought we should move them to separate class. Class name is not yet decided. In theory getTranslationFromContext, loadEntityByUuid, and loadEntityByConfigTarget should exist on EntityStorageInterface with static method on EntityInterface just like ::load, ::create but @tim.plunkett said . I don't think moving those is a right move.EntityDefinitionRepositoryshould be renamed toInstalledEntityDefinitionRepository,EntityInstalledDefinitionRepositoryorEntityDefinitionInstalledRepository@tim.plunkett agreed to rename it toEntityInstalledDefinitionRepository.s/deleteLastInstalledDefinition/removeLastInstalledDefinitionands/setLastInstalledDefinition/addLastInstalledDefinitiononEntityDefinitionRepository. @tim.plunkett agrees but it would break the BC and we don't want that here.Here is a code review.
@param block missing.
EntityDefinitionRepositoryInterface is missing from this interface. After adding that we can remove getLastInstalledDefinition and getLastInstalledFieldStorageDefinitions
This can become single statement.
Pending tests review.
Comment #15
tim.plunkett1,2,3) Still will ping one of them. Moved all 3 methods to a new EntityRepository class for now.
4) Done
5) Not done, as discussed.
1) Fixed
2) This is purposeful. In order to allow the other services to properly interact with EntityDefinitionRepositoryInterface, the other protected methods have been made public. We cannot expand the interface of EntityManagerInterface, so I left it out of the implements line and left these two here.
3) Fixed
The tests were all copy/paste since they were just rewritten in #2544746: Rewrite EntityManagerTest to use phpspec/prophecy. We have a lot of test coverage that could be added, but that's out of scope.
Comment #17
jibranLooks good to me after removal of needs tag it is RTBC for me.
Comment #18
tim.plunkettMissed a spot.
Comment #19
tim.plunkettCR: https://www.drupal.org/node/2549139
I have some @todos to clarify, I'll unassign when I think the patch is ready as well.
Comment #20
tim.plunkettOkay, that should do it. Opened #2549143: Deprecate EntityTypeRepository::clearCachedDefinitions() as a follow-up.
Comment #21
jibranThis looks ready. Just two minor points. I think CR should be in tabular form with before and after column in PHP code format so it would be easier to read. I can RTBC this but I don't think @Berdir, @yched and @plach would like that so let's wait for them to review it.
Is this still necessary?
Stray @todo.
Comment #22
jibranThis will only disruptive for existing patches in IQ.
Comment #23
tim.plunkettI think a tabular form would be useless, since the method names do not change at all, and the previous class is the same for *all* methods, EntityManager.
Tagging for one of their reviews.
1) Yes, that is needed to preserve BC, and yes we cannot inject it :(
2) Fixed. Also fixed a typo I introduced in the last interdiff.
Comment #24
dawehnerFirst: I really like that we finally split it up. It has the potential to also improve things a bit because I hope that most of those services aren't needed on runtime.
We should ensure that we don't have a worse performance after this rewrite.
Pointless interfaces in the sense that it doesn't tell you a single bit:
Wrong things in general:
OH I see so the EntityTypeManager also takes care about the initialization of handlers, mhh the name doesn't tell me at all.
Do you mind grouping them a bit better? So EntityRepositoryInterface is certainly more important. The two listeners could be next to each other.
and use #2488860: Bring phpunit bridge into drupal and use it for unit tests and simpletest to handle Deprecation and some adaption in simpletest to collect the deprecated calls. Yes the support for deprecation will be hell during the entire release cycle.
This sounds a bit wrong, it kinda tells me that the following interfaces will be removed in 9.0.0. You could then argue what is the point in adding them now :)
Comment #25
tim.plunkettNeeds a reroll after #2322503: getDisplayModeOptions() returns only full or teaser regardless of the status of the entity display.
Comment #26
piyuesh23 commented@tim
Re-rolled the patch & merged the changes from 8.0.x HEAD.
Comment #29
tim.plunkettThat deleted all of the changes that have since gone into HEAD :(
Needs another reroll.
I won't have time until Wednesday at the earliest.
Comment #31
tim.plunkettOkay I think I got it.
Comment #34
tim.plunkettMissed a spot.
Comment #36
tim.plunkettMissed *another* spot.
Comment #37
tim.plunkett#24
0) I'm going to spend more time tomorrow working on the naming.
1) I agree, but that's the pattern EntityFormInterface has chosen to use for setStringTranslation(), setModuleHandler(), and setEntityManager(). Unless you feel very very strongly about this and have a concrete suggestion, I say we leave it.
2) Done
3) EntityTypeManagerInterface is the actual plugin manager for EntityTypes
4) Reordered
5) #2488860: Bring phpunit bridge into drupal and use it for unit tests and simpletest to handle Deprecation isn't in yet, and we don't use E_USER_DEPRECATED anywhere else either. Not trying to make waves here...
6) Rewrote this comment completely
7) Yes those need to remain there in order to maintain BC. We cannot make EntityManagerInterface implement the new EntityInstalledDefinitionRepositoryInterface without adding new methods.
8) See 0
9) See 0
10) We don't even use that for the test, just removing it. Thanks for the tip though.
Comment #42
dawehnerWhat about adding a todo? These are all places we have to deal with at some point in time when we actually remove the old code.
Comment #43
tim.plunkettFixes first, renaming later.
Comment #46
tim.plunkettI cannot rename all of these myself. I came up with these names, and staring at them, I can't solve this myself.
Does anyone have any concrete suggestions?
Rerolled for #2172843: Remove ability to update entity bundle machine names.
Comment #48
tim.plunkettPIFR why are you terrible....
Comment #49
tim.plunkettWishful thinking.
Comment #50
xjmSee https://groups.drupal.org/node/484788 for more information on the rc target tag. This does sound like a potential RC target because of the minimal disruption, but tagging for a framework manager to evaluate that.
Comment #51
dawehnerThe public methods for this look like a classical repository.
That one is tricky, Its almost like a EntityField(AndFieldDefinition)Listing
looking at the interface I would suggest something like EntityTypeBundleInformation?
THe creation for the handlers could be moved into its own EntityHandlerFactory
<3 <3 <3 <3
Comment #52
tim.plunkett1) Agreed, renamed.
2) Hm, true. I think of those as similar enough to keep together
3) Reminds me of ViewsData. EntityTypeBundleInformation or EntityTypeBundleInfo or EntityTypeBundleData all work. Unchanged.
4) Just the creation? Not the retrieval? So EntityHandlerFactory would just have getHandler() and createHandlerInstance(), and EntityTypeManager would retain the specific getters? I tried this and it seemed to make sense, except getRouteProviders() and getFormObject() contain their own factory logic. Unsure of how to proceed. Unchanged.
5) :)
Comment #53
wim leersOverall: strong +1 for this direction.
This tripped me up, I wondered how it worked, but then I saw this in the interface:
Okay, so this does not use Symfony events. Out of scope to fix here, of course. Hopefully me remarking this helps other reviewers save some time.
The word "discovery" in here confuses me. It's not like we have to "discover" which fields are defined on a certain entity type, we *know* that.
Plugins are discovered, entity fields are not.
Wouldn't it be better to just say , and perhaps .
"an" implies there can be multiple operating at the same time?
Why "installed"? Why not just
EntityDefinitionRepository?Do we have entity definitions that are not installed?
Perhaps the name is fine, but then I expect that the "installed" adjective is explained on the interface.
I think the implementation's docblock's specificity belongs on the interface?
Again, implementation's docblock is more helpful than the interface's.
Comment #54
wim leersAlso:
EntityFieldManagerInterfacevs.EntityInstalledDefinitionRepository, even they offer very similar functionality/data, the former for fields, the latter for entities.Shouldn't their interfaces/services then also have similar names?
EntityFieldDefinitionRepositoryandEntityDefinitionRepositorywould make sense from my non-deep-Entity/Field-API-knowledge POV :)Comment #55
tim.plunketts/EntityTypeBundleManager/EntityTypeBundleInfo
s/EntityFieldManager/EntityFieldDefinitionRepository
s/EntityInstalledDefinitionRepository/EntityDefinitionRepository
That's it for today, be back tomorrow.
Comment #56
dawehnerSorry wim but this change is just fundamental wrong. If you look at the methods this is ALL about the installed definitions, which clearly makes a difference compared to the entity definitions you can retrieve directly from the entity manager.
Mh, its not just about field definitions if you are honest about it.
Comment #57
wim leersDaniel:
Comment #58
dawehnerWell, just saying :)
Comment #59
yched commentedAgreed that EntityDefinitionRepository having a method to get FieldStorages (getLastInstalledFieldStorageDefinitions()) is a bit confusing when there is a EntityFieldDefinitionRepository next to it.
The methods currently grouped in EntityDefinitionRepositoryInterface (former EntityInstalledDefinitionRepositoryInterface) are about getting the "last installed" definitions for things (entity definitions and field definitions) whose "current" (usual) definitions are normally handled by two separate repositories (resp. EntityManager and EntityFieldDefinitionRepository)
So the only thing that "groups" those otherwise separate methods is the "last installed" aspect (which, I agree, is not too intuitive off-hand) :-)
If I'm not mistaken, "last installed" is to be understood with respect to the schema, right ? So maybe EntityLastInstalledSchemaRepository Interface would be slightly clearer than just EntityInstalledDefinitionRepositoryInterface ?
Comment #60
tim.plunkettReverted the EntityFieldDefinitionRepository and EntityDefinitionRepository changes, and renamed it to EntityLastInstalledSchemaRepository.
Comment #61
fagowow, impressive work + patch. Generally, big +1 on the approach - EM really needs to be split up. Given the BC-layer I do not see a problem with it doing that after RC also.
Good it's gone, I do not like using repository for field definition.s Afaik Repository is mostly used for handling persistence elsewhere, and as field definitions are generally not persisted this would not be a good fit imho.
Comment #62
tim.plunkettKeeping up with HEAD. #2488568: Add a TypedDataManagerInterface and use it for typed parameters just changed from TypedDataManager to TypedDataManagerInterface.
Comment #63
tim.plunkettFixed minor conflicts with #2533800: Remove all unused use statements from core.
Comment #64
tim.plunkettComment #65
effulgentsia commentedTagging for triage per #50. Sorry it's taking a while to actually get to the triaging.
Comment #66
effulgentsia commentedLOL. Just as I wrote #65, @catch and I finished discussing this and decided to approve this for RC, so retagging. Note that when one of us reviews the patch itself, we may have feedback, but in principle, having different interfaces and different service IDs for the different subparts, while keeping the giant interface and service ID for BC, seems low disruption and a good benefit to contrib modules that need to do significant interaction with subparts of entity management.
Comment #67
tim.plunkettRerolled after docs changes in #2590605: Followup for [#2322503].
Comment #68
dawehnerJust some more review`.
Its sad how many dependencies this particular service has
I'm curious whether we could/should reflect this flag on the cache backend. For some reason I would also expect an 'a' somewhere in this sentence.
So yeah thinking more about it, it maybe should be named EntityBundleCrudListenerInterface and similar in the first place?
Btw. according to the interface its a CD only
I like this bit of documentation
I'm curious whether we can find a better name to something more selfdescriptive, like EntityTypeCrudListener
This itself seems way more useful than the docs on the interface to be honest. Keep in mind that people see the interface only
OT: Just in general, Its crazy that we don't store that information but rather determine this always on runtime
Can we put anything online there?
Can we put an @see to the event classes to connect things a little bit more here?
Some question regarding the naming ... just to be clear, the classname doesn't always have to follow the interface strictly ... that is the entire point of having an interface
Ideally we would solve that in its own issue, it just feels weird to change DefaultPluginManager here as well
Comment #69
tim.plunkett1) Yeah, Entity fields are complex.
2) Fixed the docblock. Unopinionated on changing cache backends directly.
3) Yeah the interface is a bit off, but that's in HEAD.
4) :)
5) It also technically is only CUD. Also same as 3, the name of the interface is in HEAD
6) Changed.
7) Agreed.
8) Wasn't this 6? ^
9) Did that here and on EntityTypeListener
10) True, but I have no better suggestion.
11) I'd split it out to another issue if we weren't dealing with rc target stuff...
Comment #70
dawehnerThat was probably "useful"
Are we sure that this is a valid argument when it comes to rc target stuff? Dont' we still want small reviewable pieces so we can even much better see errors before they come up unlike what we do now with one big big patch.
Comment #71
tim.plunkettPer a conversation with @dawehner and @alexpott, switching the BC-only EntityManager to use the container directly, in order to cut down on needless service instantiation and code loading. Removing the 'needs profiling' tag accordingly.
You can see in the interdiff how much less code we need to load in EntityManagerTest!
Comment #72
dawehnerI had one question ... why we still have methods on the entity manager interface, but well, this is due to the problem that the
EntityLastInstalledSchemaRepositoryInterfacehas more methods, so letting EMInterface extend that would actually be a BC break.In general this patch is a really great step towards a more sane future
Given that catch is a entity subsystem maintainer I think this could be marked as RTBC
Comment #74
tim.plunkettComment #75
alexpottAssigning to catch since catch is both a committer and an entity subsystem maintainer
Comment #76
tim.plunkettReroll for #2597844: EntityManagerTest doesn't assert everything it thinks it does.
Comment #77
catchOK didn't review the whole patch and in fact tried not to since a lot of it is just moving code.
However found a few things that both looked new and I had questions about.
Unassigning me, I'm happy for this to go in, makes things so much clearer where the dependencies are, even if have minor issues with the stuff below.
The classname and location makes it look like a trait that cache backends would use, but instead it's a trait for using cache backends in a certain way - skipping caching.
Could use more documentation on when to use it, and possibly a rename.
There's no static caching here, so it's more whether the cache should be read from or written to at all.
Also the API here is incomplete, reading it I'd expect it to handle other CacheBackendInterface methods.
Why does the deprecated method have to be on the brand new interface? Couldn't we leave that one on EntityManager for bc only?
Look at that <3
Could use a bit more context.
Same question with the deprecation - why not leave this in EntityManager and not bring it over here?
Comment #78
tim.plunkett0) Good point. Renamed to UseCacheBackendTrait (no better ideas).
1) Clarified
2, 5) EntityFieldManager and EntityTypeRepository were abusing \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface (which has clearCachedDefinitions() and useCaches()), but they don't support the rest of plugin discovery. Regardless of EntityManager still existing for BC, these methods need to exist until the API can be rewritten. That's why they have their own follow-up issue #2549143: Deprecate EntityTypeRepository::clearCachedDefinitions()
3) :)
4) #2603542: Deprecate \Drupal\Core\Entity\EntityFormInterface::setEntityTypeManager()
Comment #79
tim.plunkettAlso I think #77 counts for this tag.
Comment #80
alexpottTicking credit boxes for @dawehner and @Wim Leers cause @tim.plunkett asked me to.
Comment #81
alexpottCommitted f0928e5 and pushed to 8.0.x. Thanks!
Comment #84
loopy1492 commentedI found this whole new methodology for querying entities confusing. I finally got my solution working, but I decided to write up a more detailed description of what I did in case anyone else needs it. C&C welcome.
https://www.drupal.org/node/2549139#comment-13381841