Problem

At the Drupalcamp Vienna code sprints I've seen people doing stuff like

     /**
       * @var \Drupal\node\NodeInterface $node
       */

such that they are able to auto-complete on the methods and can work with it. That's necessary as calls like

   $node = \Drupal::entityManager()->getStorageController('node')->load($id);

does not give you auto-completion.

Another problem with the above line is that you'll have to know about storage controllers, access controllers, view builders, etc. for being able to work with an entity type. But you shouldn't have to. It's fine that we use entity controllers for easy entity type construction and code-reuse, but that's not something a person working with an entity type should have to know about.

Finally, imo we want to have a single object that we can inject into entity class, instead of having to add separate dependency for each controller being used there as that makes for quite some code and is not efficient memory-wise (every class property increases memory usage). Instead, we could inject the entitymanager but you'd still have to do calls like $this->entityManager->getStorageController($this->entityType)->save($this); - ouch. Instead, having a single per entity type manager class injected would be way more convenient.

Solution

Something along the lines of (to be figured out):

  • Provide per entity type managers, i.e. NodeManager, CommentManager (exists!), UserManager, NodeTypeManager, ... and make all important methods available on it.
  • Re-define methods critical methods as required for type-hints on it.
  • Rename EntityManager to EntityTypeManager, as when you do $entity_manager->getDefinitions() it gets you definitions about entity types, not entities.
CommentFileSizeAuthor
#2 static_load-do-not-test.patch721 bytesamateescu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fago’s picture

So I shortly sat down with sun to shape a possible solution:

1. Rename EntityManager to EntityTypeManager
2. Make an EntityManagerBase which has everything needed + gets the EntityManager injected
3. Entity types providing modules have to provide a respective service, along with a suiting class.

Downside:
Point 3) requires entity type providing modules to provide a respective class + service. We might be able to figure out providing the service automatically, but people would at least have to provide a simple manager class. I'd expect people to start using code-generation tools for there entity types in future anyway, so I don't see that as a big problem.

Given that one could do:
$nodeManager->load()->isPublished(); // Auto-completion for isPublished() works.
while we could inject the node manager into nodes to do
$this->manager->save($this);

The class could be defined similar to the following:

         class NodeManager extends EntityManagerBase {

            protected $entityType = 'node';

            /**
             * @return NodeInterface
             */
            function load($id) {
              return parent::load($id);
            }

            /**
             * @return NodeInterface[]
             */
            function loadMultiple($ids) {
              return parent::loadMultiple($ids);
            }
          }

Other methods like
$manager->view($node) or $manager->access($node, 'view') be inherited from the base class.

A problem lies in entity types which are not using some of the available controllers, so node types would not have a working $manager->view() right now. Either we live with that (what encourages people to implement the whole set) and figure out suiting handling for that case or we use traits to add in the respective methods only when needed.

Lastly, this would not cover contrib module controllers - but still it fixes DX for the core cases which should cover the most important use-cases already.

amateescu’s picture

FileSize
721 bytes

How about something much simpler: in the patch attached getEntityTypeFromClassName() would keep a entity_class => entity_type mapping in a static cache, and Node::load() in an IDE will offer autocomplete suggestions with methods from the Node class.

msonnabaum’s picture

I like the approach in #2, it's very similar to something I proposed here https://drupal.org/comment/7901669#comment-7901669.

I could live with either though.

I would ask that we please don't call these Managers. EntityManager is vague enough to possibly warrant the name, but once we make them entity type specific, they start looking a lot more like Repositories, which implies a much clearer set of responsibilities.

Berdir’s picture

Isn't it the storage (controller) that's the repository? I can't see what the manager (in current speak) and the storage then separates?

It's also not clear to me how this would help with type hints, you'd need to a) provide a static class + method similar to \Drupal or have an injected manager/controller that you then need to type with @var (same as you can already do right now) otherwise this won't work.

There are also various issues that suggest to expose each entity controller/handler/whatever as a separate service.

The issue about adding public methods to the container and use those for autocomplete would help, but only if you have a running installation with the module that you're working with installed.

amateescu’s picture

+++ b/core/lib/Drupal/Core/Entity/Entity.php
+   * @return static
...
+  public static function load($id) {

I'm not sure exactly which patch you're talking about, but this little snippet allows Node::load() to typhint methods from the Node class.

fago’s picture

Isn't it the storage (controller) that's the repository? I can't see what the manager (in current speak) and the storage then separates?

Yeah, I'd see at that way as well. Would you expect a viewMultiple(), access(), or form() method on the repository?

#2/#5 seems interesting, but where does that lead us compared to the manager approach. Would we bring respective helper like viewMultiple() or form on the class as well? Then, we'd end up with quite some static methods + would have to inject all the various controller in each entity instance?

Lastly, #2 would not encourage or even work with DI, while a NodeManager service would. And it does that while making it less tedious compared to having to inject multiple controllers or just the entity manager ($this->entityManager->getStorageController($this->entityType)->save($this); - ouch)

amateescu’s picture

We already have(/had) those helpers in the form of node_load(), node_load_multiple(), node_save(), etc.. my proposal would just bring all of them in one place (the Entity class) so we can get rid of the procedural helpers of every entity type for good and without turning DX into \Drupal::entityManager()->getXController($entity_type)->doStuff(), and also without having to create yet another class (a Manager) for each entity type.

fago’s picture

Right, that sounds good in general - but it does not work as injected dependency. Once you'd work with dependencies, you'd have to go with various controller objects or the general entity manager again?

I'd assume we should encourage people to use DI, so it should work flawlessly - what an injected ${ENTITYTYPE}Manager would allow? Or is that not worth the troubles?

I figured, there is one problem with that approach though. Entit-type specific storage methods (e.g. load term children) would still be only on the storage controller, thus accessing them does not work so neatly any more or people would have to re-define and forward them on the entity-type specific manager (ouch!).

Berdir’s picture

I don't see how a per entity type manager helps with type hints, it's exactly the same as now, the problem is that you would have to override every single method to add the correct @return for those methods. You can already inject the entity manager and then assign the node storage controller and document it as @var NodeStorageController, but that doesn't change the fact that load() is documented as @return EntityInterface.

fago’s picture

Right. The per entity-type manager removes the burden of having to know about all the different controllers from developers though, and makes DI and working with the API way more convenient as you'd have everything in one place, compared to having to inject multiple dependencies + caring about adding another dependency each time it turns out you need to call another method living elsewhere. E.g., a random page callback controller class that would load and render some related entities would need
- storage for loading
- access for checking view access
- view builder for viewing
- entity manager for getting the right translation context
...
Having all entity-related APIs available via a single entity-type specific manager would provide a better DX here, imo.

So, the alternative would be injecting just the entity manager + getting everything from there. I have been thinking that's where we are heading, but $this->entityManager->getStorageController('node')->load(); calls are not only needlessly verbose (as you have to specify the entity type on each method call), but moreover have we lost the possibility to do per entity-type type hints with that calls.

Berdir’s picture

Again, I don't see how this would be anymore type specific than the storage controller, both are just @return EntityInterface unless we'd override all methods just to document them differently.

In your example, access and correct translation stuff is available on the entity, so all you need is the storage and view builder.

Also. wouldn't this make cases where the entity type is dynamic more complicated?

It would also enforce the first (controllers that have a specific method on the entity manager) and second class (additional ones that need go to through the generic method) controllers, by making them work completely different. Yes, they are usually not really a public API (e.g. views data generation), but still..

I'm not against it, I just can't see (yet) how this would benefit us/see more problems than things that it would solve.

amateescu’s picture

IMO, injecting a manager is almost always wrong, your class needs to know exactly what it will use, e.g. just the storage controller. An approach of "give me everything you have and I'll see later what I'll use from it" is as "good" as injecting the whole container..

With that in mind, I think developers should absolutely know about all the entity controllers, we're not doing anyone any service by hiding them.

What really helps DX is type hinting in an IDE, and, as Berdir points out, the 'manager per entity type' approach does not provide that..

fago’s picture

What really helps DX is type hinting in an IDE, and, as Berdir points out, the 'manager per entity type' approach does not provide that..

Not alone, but it makes it possible. Once we've overridden NodeManager::load() to specify @return NodeInterface, I could do
$this->nodeManager->load().
But when I override node's storagecontroller's load right now, $this->entityManager->getStorageController('node')->load() still won't pick it up.

Alternatively, it would also work with $this->nodeManager->getStorage()->load() + overridding node's storagecontroller's load.

IMO, injecting a manager is almost always wrong, your class needs to know exactly what it will use, e.g. just the storage controller. An approach of "give me everything you have and I'll see later what I'll use from it" is as "good" as injecting the whole container..

Give me a "nodemanager" is surely not the same as a "give me everything" - it's a question of getting the granularity right. Being able to say I want to work with "nodes", so give me its manager and I'm good would be convenient, while give me node A, B, C and oh Z as well isn't so.

That said, our entity controllers do a good job in separation of concerns and code re-usability. But is the knowledge of which controller is in charge of what really something anyone working with entities should have to worry with?

Putting shortcuts on the entity object itself is definitely something that works and helps to solve the problem, but it doesn't solve it for all methods working with multiple entities. So that could be a good rule of thumb, have multiple helpers on the manager, others go on the entity class.

In your example, access and correct translation stuff is available on the entity, so all you need is the storage and view builder.

getTranslationFromContext() isn't on the entity, yeah access() is, but e.g. createAccess() isn't. So you can re-phrase my example and also display a creation link if you want ;-)

Also. wouldn't this make cases where the entity type is dynamic more complicated?

I don't think so. For one you can still work generally with the current EntityManager, then I think we can make it possible to work with the type specific manager generally as well - e.g. do $entityManager->getManager('node')(not sure about the right terms here, but you get the idea).

It's actually quite nice that we've domain-specific methods on the entity type specific interfaces, like $node->isPublished(). But without that last step I don't see it becoming very useful and handy - it's all there, but by default hidden :-/

It would also enforce the first (controllers that have a specific method on the entity manager) and second class (additional ones that need go to through the generic method) controllers, by making them work completely different. Yes, they are usually not really a public API (e.g. views data generation), but still..

True, but I think that's the case already as we only provide convenient access methods for "known" controllers. I don't see that as a big problem - we can do what we can to ease usage of what we know, what's still better than leaving everything with a generic $entityManager->getController(...)

effulgentsia’s picture

Per discussion with catch, this is an approved API change (I'm assuming that this will involve some changes, not merely additions), but per http://buytaert.net/the-next-step-for-drupal-8-is-a-beta, this is a critical API, so if it's going to happen, it should happen before beta. If this issue is raised to critical at some point, that would also grant it "beta blocker" status, but as of now, it's merely a "beta target".

xjm’s picture

Priority: Major » Critical
Issue tags: -beta target +beta blocker

Discussed on the phone with @Dries, @webchick, @msonnabaum, @tim.plunkett, and @alexpott -- this should be considered a beta blocker given the DX impact.

Berdir’s picture

That is interesting, because I still don't understand the DX impact/improvements :)

It only helps with IDE auto-complete if you provide a class (+interface?) that overrides every single relevant method and fixes the @return, which would then again conflict with the current definition of @inheritdoc which does not allow partial changes (there's an issue to discuss that). Unless we'd be able to define a way similar to java generics, where you could somehow specify "every method that returns EntityInterface now returns NodeInterface" in a single place, that seems to be quite a burden and I know that some people are quite strongly against adding more glue/wrapper code.

It would save the getStorageController()/getViewBuilder()/... call when called directly, but that is mostly relevant for static calls that do something like \Drupal::entityManager()->getStorageController('node')->load(1), which would then maybe look like \Drupal::entityManager('node')->load(1), which there's still no chance at all for autocomplete and for static calls, I think @amateescu's patch in #2 (that would allow Node::load(1)) is a more interesting approach. We should open a separate issue for that.

tim.plunkett’s picture

I'm having a hard time understanding what is proposed here.

Can we get some concrete before/after code examples please?

tim.plunkett’s picture

Also, please look at SearchPageRepository and how it is used, and consider if that might be a better approach.

xjm’s picture

From #16, the intended beta blocker was "do whatever it is we need to do so that the DX for entity CRUD is not terrible", because we want the entity API to be reasonably stable in the beta and have DX that module maintainers will fall in love with. :)

So, improve this:
$node = \Drupal::entityManager()->getStorageController('node')->load($id);

Something like #2 would be great, if that's acceptable. If that wants to be filed as a separate issue rather than re-scoping this one, that's fine -- but whatever gives us something sane-looking and IDE-friendly like Entity::load() (or whatever) is what's beta-blocking. If this issue doesn't have consensus and/or won't give us that, let's dial it back to a major and move the beta blocker tag to the one that will. :)

Berdir’s picture

Yes, so what this could give us is this, if I understand it correctly:

\Drupal::entityManager('node')->load($id)

However, that doesn't help with type hint/useful @return documentation in anyway and we could also do if we're just looking for a way to shorten that call:

\Drupal::entityManager()->load('node', $id);

Not much of a difference, neither of them tell you that you're working with a Node/NodeInterface object.

Node::load($id)

is the only one that could more or less do that. So +1 on opening a separate issue for that, note that #2096899: Add $EntityType::create() to simplify creating new entities hasn't really made any progress since prague, though.

However, that won't solve the (type hint) problem with services, when we want to inject the storage controller, then call $storage->load($id). But neither would this issue help.

amateescu’s picture

xjm’s picture

fago’s picture

#22 sounds good in general, but it are special solutions for load() and create() only. Then, would this mean code should not use dependencies and use Node::load() instead? This doesn't seem to be a reasonable suggestion while we are shooting for a dependency injected world.

Wouldn't it be better to have a NodeManager instead which can have overridden create/load() methods with improved documentation? Maybe this could be even auto-generated?

Plus, we could do the shortcut
Node::getManager()->load($id);
for non dependency-injected code.

jhedstrom’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: -beta target

Tagging/bumping to 8.1 as I don't think this is able to happen for 8.0 at this point.

jhedstrom’s picture

Version: 8.0.x-dev » 8.1.x-dev

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)
Issue tags: -Needs issue summary update

Closing as outdated since this moved to PNMI 7 years ago.

If still an issue in 9.5 please reopen with an updated issue summary.

Thanks!