Problem/Motivation

In a recent profiling run (http://rpubs.com/msonnabaum/22598), we saw slowness in node_type_get_types() where a lot of time is spent in \Drupal\Core\Entity\Entity::getEntityTypeFromStaticClass. The page which generated this data has a table View so it didn't benefit from render cache (yet).

Proposed resolution

getEntityTypeFromClass() is moved to EntityManager class and simplified. EntityType class now has getOriginalClass() method.

Remaining tasks

None

User interface changes

None

API changes

Node types have traditionally been able to opt out of the standard node_access() behavior by setting the node_permissions_$type variable to 0 during hook_install. This feature has been removed. Any modules using this feature are encouraged to create own entity type where this control and more are readily available.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

moshe weitzman’s picture

Berdir’s picture

I guess when we added this we said it's not going to be called very often - ouch.

Looks like the calls are coming from node_permissions_get_configured_types(), which is called from node_node_access(). Which seems to be a pretty weird feature, I don't think that's documented in config schema? Or tested?

Anyway, it would be relatively easy to add a static cache to that method, with the risk that in some very rare scenarios (one of those I just looked at today) could mess things up.

We should possibly also statically cache node_permissions_get_configured_types(), because calling NodeType::loadMultiple() 150 times isn't going to be free. especially without config entity static cache.

Can you share what exactly was profiled or provide access to the full xhprof report?

Berdir’s picture

Another thing that might help a bit is removing the additional logic that we added to more explicitly deal with multiple exact matches on the entity class/type, which @fago wanted. Before, we returned on the first exact match, now we throw an exception if there's more than one, so we always have to loop over all of them.

msonnabaum’s picture

Attaching full xhprof. Not sure if this is the same run, but it's a similar one.

We could easily throw a cache on it, but I'd love to see if we could just simplify the whole process. That's a hell of a lot of work for what it's doing.

moshe weitzman’s picture

Looking at node_permissions_get_configured_types, can we just say that all node types should be managed by permissions. If you want different behavior, you should create your own entity type. I feel like this function carries forward a bunch of old Drupal baggage.

Having said that, we still need to speed up node_type_get_types(). I can add a static cache there (and add a $reset param to the function?). I'd need some guidance on other solutions.

Berdir’s picture

+1 to removing node_permissions_get_configured_types(), which will kill the calls to node_type_get_types() as far as the profiled page is concerned.

I'm not sure about simplifications for the method. As said, one approach would be to remove the nice exception for multiple exact matches, which actually breaks stuff now.

Another is that we now have enough usage of those to try and go back to msonnabaum's initial classname conversion approach to see if that works. Would imply that a bunch of classes (Like Term and Vocabulary) will need to override that method, which is something we could live with to get a performance boost.

Damien Tournoud’s picture

Alternatively, the bulk of the logic in Entity::getEntityTypeFromStaticClass() could be move to the entity manager (ie. something like EntityManagerInterface::getEntityTypeFromClass($classname)), where we could easily add caching.

Berdir’s picture

Moving it to the entity manager sounds like an interesting idea too.

This removes that function. Unsurprisingly, doesn't look like we have test coverage for this. Maybe this should be done in a separate issue?

Berdir’s picture

Status: Active » Needs review
msonnabaum’s picture

I had the exact same thought as #7.

I'd still prefer simplifying the process if possible, but if we can't, breaking it up and moving most of it to entity manager makes sense to me.

Berdir’s picture

Something like this?

- Moved the method and added static caching.
- I also fixed a problem with the method while I was at it if you have multiple subclasses *and* an exact match that is not returned first.
- The unit tests need to be cleaned up, most of it should probably move to EntityManagerTest, only the part about passing the correct class name around needs to be kept in EntityTest. Just made them not fatal for now, easier to see if other tests have a problem with this, but I doubt that.

Status: Needs review » Needs work

The last submitted patch, 11: entity-type-from-class-2307681-11.patch, failed testing.

msonnabaum’s picture

It is much better on the EntityManager, but this method still feels very complex. It could be broken up some, but it's unclear to me why we even need to do some of these checks.


// Check if this is the same class, throw an exception if there is more
// than one match.
if ($entity_type->getClass() == $class_name) {
  $entity_type_id = $entity_type->id();
  if ($same_class++) {
    throw new AmbiguousEntityClassException($class_name);
  }
}

What is the scenario where this would actually happen? Why is it not ok to just return once a match is found?

msonnabaum’s picture

Also, if I'm reading this right, one invocation of this method will autoload all defined entity classes because of those is_subclass_of calls. I'd like to make sure that check is absolutely necessary before we accept that.

Damien Tournoud’s picture

Also, don't we have most or all of this information from the annotation discovery anyway?

Damien Tournoud’s picture

So the use case for the is_subclass_of is to be able to swap out an entity type class MyEntityType with a child class MyEntityTypePatched without breaking direct calls to MyEntityType::load() in existing code.

That really an information that we should add to the entity type discovery information.

Berdir’s picture

#13: The reason this could happen is if you dynamically expose multiple entity types with the same class, see https://www.drupal.org/project/eck as an example that could do it like this in 8.x. It was not in the original implementation, but @fago was strongly for adding it, and I just wanted to get it in, so I included that. It will never work for those, but without it, it will just pick the first, while that code will force an exception.

#16: Hm, now that we have EntityType as an object for this information, we could possibly have a getOriginalClass() method and the class would ensure that the originally set class would be returned there? Yes, then we might be able to get rid of this logic..

Berdir’s picture

What do you think about this approach?

Also updated the unit test coverage so that the separate methods are separately cached, removed a lot of complexity from EntityUnitTest, as we now only need to test smaller snippets.

msonnabaum’s picture

This is certainly a big improvement. It still feels like the check for duplicates belongs elsewhere, but I can live with this if necessary.

Berdir’s picture

Yeah, I'd be fine with removing that too, but @fago explicitly requested it and I didn't want to go behind his back on this one.

cbr’s picture

Fixed a issue where the subclass entities could not be loaded anymore, tested on a live environment.
Now allowing the actual class name as well, this makes sense right?

New unit test also included in the patch.

Status: Needs review » Needs work

The last submitted patch, 21: entity-type-from-class-2307681-21.patch, failed testing.

moshe weitzman’s picture

Patch now fails to apply. Will you reroll, @ChrisjuhB?

cbr’s picture

reroll

cbr’s picture

Status: Needs work » Needs review

needs review

cbr’s picture

FileSize
31.58 KB

reroll needs review

The last submitted patch, 24: entity-type-from-class-2307681-24.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 26: entity-type-from-class-2307681-26.patch, failed testing.

moshe weitzman’s picture

Drupal\Tests\Core\Entity\EntityResolverManagerTest 12 passes
PHP Fatal error: Call to undefined method Drupal\Tests\Core\Entity\EntityTypeTest::randomName() in /var/lib/drupaltestbot/sites/default/files/checkout/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php on line 209
PHP Warning: Invalid argument supplied for foreach() in /var/lib/drupaltestbot/sites/default/files/checkout/core/scripts/run-tests.sh on line 585

Warning: Invalid argument supplied for foreach() in /var/lib/drupaltestbot/sites/default/files/checkout/core/scripts/run-tests.sh on line 585

From https://qa.drupal.org/pifr/test/839273

cbr’s picture

Status: Needs work » Needs review
FileSize
1.12 KB
31.58 KB

Changed randomName() to randomMachineName().

Status: Needs review » Needs work

The last submitted patch, 30: entity-type-from-class-2307681-30.patch, failed testing.

cbr’s picture

Status: Needs work » Needs review
FileSize
31.62 KB

Reroll

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

OK, thanks for making tests more specific. Looks like everyone has been satisfied. If I'm wrong, feel free to change status.

Berdir’s picture

We need a change record for the removal of the configurable permission stuff (it would not work right now anyway without the schema and anything, but still) - if we want to do this here.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs issue summary update

Yep let's get a draft record done for the removal of node_permissions_get_configured_types(). Also the issue summary seems out of date.

fago’s picture

yeah, moving this to the EM makes sense. I also like how the patch moves to keeping the original class - that makes the detection more robust already.

This is certainly a big improvement. It still feels like the check for duplicates belongs elsewhere, but I can live with this if necessary.

Where would you put it then instead? I think we either need to be sure that the returned entity type is correct by checking as we do now, or document to require a unique entity class by entity type and rely on that. I still think that just silently returning a wrong entity type is a WTF and the wrong thing to do.

moshe weitzman’s picture

Issue summary: View changes
moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record, -Needs issue summary update

Created draft CR and updated issue summary

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

Drats!

git ac https://www.drupal.org/files/issues/entity-type-from-class-2307681-32.patch
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 32382  100 32382    0     0  63547      0 --:--:-- --:--:-- --:--:-- 65418
error: patch failed: core/lib/Drupal/Core/Entity/EntityManager.php:11
error: core/lib/Drupal/Core/Entity/EntityManager.php: patch does not apply
moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs reroll
FileSize
31.61 KB

Fortunately not much decayed. Just two use statements conflicted.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 9d77777 and pushed to 8.0.x. Thanks!

  • alexpott committed 9d77777 on 8.0.x
    Issue #2307681 by cbr, Berdir, moshe weitzman:...
moshe weitzman’s picture

Thx. I just published the CR. Hope thats the right workflow.

larowlan’s picture

This broke phpunit code coverage reports - see
#2324293: PHPUnit code coverage broken

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.