Problem/Motivation

Entity classes currently fetch their entity information in the __construct() method. This has a number of disadvantages:

  • It makes them unecessary large
  • It requires special care during serialize/unserialize to remove and re-add the entity information
  • And finally, it makes it impossible to instantiate/unserialize an entity during bootstrap. A typical case for that is serialized entities in variables or the session. While that is obviously not recommended, it can happen (it does for example a lot in tests because sent mails including context are saved as a variable).

Proposed resolution

Remove the early-fetched entity info from the entity class and request it on demand. This is slightly slower but this is micro optimization and hardly measurable in a normal page load (especially since the id() method is not yet used at all). And if they show to become relevant, it is easy to make it much faster than it is even now by hardcoding a few things, like the id() method, as suggested by fago in #3.

The detailed performance testing explanation is in comment #7, calling id() currently takes 0,56 microseconds, takes 1,88 microseconds with the suggested patch and would be 0,25 microseconds with the property hardcoded.

This will allow to remove two hacks in #1361228: Make the user entity a classed object which are currently necessary to circumvent the bootstrap issues outlined above.

Remaining tasks

The patch and approach needs to be reviewed and approved. First if the current approach is OK and second, if the hardcoding of the id() implementation should already be done now or revisited later once core has been ported to actually use that method when there are better numbers available as to how often the function is called.

User interface changes

None.

API changes

The (protected) entityInfo, idKey and bundleKey properties are removed. In case entity classes have been using them, they need to change the code to use entityInfo().

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Berdir’s picture

This seems to be quite simple, that stuff is never used outside of the entity class anyway and there already is entityInfo(), so I've just added two protected helper methods called idKey() and bundleKey().

This also makes the whole __sleep()/__wakeup() stuff unecessary.

Let's see what the tests have to say about this.

Berdir’s picture

Status: Active » Needs review
fago’s picture

Title: Add methods to lazy load entity information instead of prefetching it into properties » Remove entity info from the Entity classes
Status: Needs review » Needs work

Yep, having the entity-info lying around there was already criticized multiple times. I don't like it either so let's try to improve that.

+++ b/core/modules/entity/entity.class.inc
@@ -186,26 +164,36 @@ class Entity implements EntityInterface {
   public function id() {
-    return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
+    $id_key = $this->idKey();
+    return isset($this->$id_key) ? $this->$id_key : NULL;
   }

What about removing the idKey() method/property at all and just go with the id() method everywhere in the class + do the lookup directly in id()? That should be faster and cleaner.

Then, we can implement the id-key lookup just directly in id(), whereas entity type classes could override it and just hardcode the id-key there. Although it's a bit of information duplication, I'd say the class would be even cleaner and it would be a bit faster too.

Thus we'd have:

class Comment extends Entity {

  public function id() {
    return $this->cid;
  }
}

Analogously the bundle. Thoughts?

fago’s picture

Status: Needs work » Needs review
yched’s picture

Just a note that "how does a [field api widget|image effect|input filter...] access the $info that lies in its hook_info() entry" is also a question in the in-progress Plugin system. Not sure whether entity types and controller classes will actually leverage the Plugin system at some point, but that $info stuff is a common pattern.

I'm currently embedding a copy of that $info in the FieldTypeHandler, WidgetHandler... classes in my "OO Field API" sandbox, but I'm not too happy with that either.

Berdir’s picture

FileSize
5.01 KB

Removing them works too, here is a patch for that. Also removed bundleKey(), that was only used in bundle() as well.

Hardcoding the property in the classes sounds like an idea, I'll try to make some tests to see if there is a big difference in regards to performance.

Berdir’s picture

Some very basic tests with the following code:

$comment = comment_load(1);
$start = microtime(TRUE);
for ($i = 0; $i < 1000000; $i++) {
 $id = $comment->id();
}

$time = microtime(TRUE) - $start;

echo t('Calling id() 1000000 times took @time msec, @mtime microseconds per call.', array('@time' => round($time * 1000, 2), '@mtime' => round($time * 1000 * 1000 / 1000000, 2))) . "\n";

HEAD:
Calling id() 1000000 times took 563.08 msec, 0.56 microseconds per call.

With this patch:
Calling id() 1000000 times took 1877.34 msec, 1.88 microseconds per call.

With the harcoded id() method from #3:
Calling id() 1000000 times took 251.57 msec, 0.25 microseconds per call.

That said, we might end up calling id() quite often but most likely not often enough to worry about a microsecond :)

Berdir’s picture

Issue summary: View changes

Updated issue summary. (Berdir)

Berdir’s picture

Issue summary: View changes

Updated issue summary.

Berdir’s picture

Issue summary: View changes

Updated issue summary.

Berdir’s picture

Issue tags: +Entity system

Added tag

catch’s picture

Spoke to Berdir about this a bit in irc.

Since we specify the class in the info hook, it seems good to me if we can stop the class itself calling back to entity_get_info() as much as possible - i.e. more or less what fago suggests in #3.

fago’s picture

Yep, I'd like the solution of #3 most.

@performance:
Thanks for the benchmark! We might not call $entity->id() 1000000 times per page load, but still getting the id() is very foundational and a difference of 1 : 7,5 is rather huge. Also this is about $entity->bundle() too which is checked quite often; e.g. 1000 calls would already make a difference of ~1.5ms.

Berdir’s picture

Assigned: Unassigned » Berdir

Ok, sounds like we have an agreement to do the following:

- Change default implementation of id() to return $this->id, provide overrides for current entity classes
- Change default implementation of bundle() to return entity_type, provide overrides as necessary.

Postpone additional discussions related to e.g. url/label default implementations to follow-up issues.

Will work on this later today I hope.

Edit: Changed NULL to entity_type for bundle() default value.

fago’s picture

- Change default implementation of bundle() to return NULL, provide overrides as necessary.

Let's have bundle() default to the entity type as this is the default for no-bundles as defined by the interface. (That default should probably change too, but that's another issue again ;)

Berdir’s picture

FileSize
6.07 KB

Ok, the attached patch implements this, let's see what the testbot has to say about this.

I noticed that the additional properties which are added to comments in attachLoad() aren't documented as properties, sounds like another follow-up. I guess we should also try to untangle comments from users because this currently breaks the entity caching (changing a username does not update the cached comments written by that user) but that's yet another issue.

Status: Needs review » Needs work

The last submitted patch, hardcoded.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
1.02 KB
6.7 KB

Ok, this should pass the tests.

- Used enforceIsNew() instead of unset($term->tid) (which actually is able to remove a explicitly defined property, weird).
- Added an isset($this->id) to the default implementation. We can't add a public $id there as that define it for all entity types that extend from Entity.

Status: Needs review » Needs work

The last submitted patch, hardcoded2.patch, failed testing.

tstoeckler’s picture

+++ b/core/modules/entity/entity.class.inc
@@ -226,7 +195,7 @@ class Entity implements EntityInterface {
+    return $this->entityType();

Seems like this could use the entityType variable directly, instead of the method ?!

The failing tests seem to be caused by a similar unset in TaxonomyVocabularyUnitTest::testUninstallReinstall()

tstoeckler’s picture

This fixes #17 and fixes the remaining test, at least locally.
Interdiff to #15 attached.

tstoeckler’s picture

Status: Needs work » Needs review

Marking needs review.

fago’s picture

FileSize
7.43 KB
+++ b/core/modules/taxonomy/taxonomy.entity.inc
@@ -75,6 +75,22 @@ class TaxonomyTerm extends Entity {
   public $vocabulary_machine_name;
+
+
+
+  /**

Too many empty lines. Fixed.

+++ b/core/modules/taxonomy/taxonomy.entity.inc
@@ -263,6 +279,15 @@ class TaxonomyVocabulary extends Entity {
   public $weight = 0;
+
+
+
+  /**

Again. Fixed.

+++ b/core/modules/taxonomy/taxonomy.module
@@ -1532,7 +1532,7 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc
       $term = entity_create('taxonomy_term', $item);
-      unset($term->tid);
+      $term->enforceIsNew();

enforceIsNew() does not unset the term id, but is for keeping the set term id. However, 'autocreate' is no valid term id. Thus, fixed that so 'autocreate' is never passed to the entity system.

Updated patch attached. Should be good now.

Also I've added the patch to the entity-conversion sandbox over at http://drupal.org/sandbox/fago/1497344 under the entity-info branch. Please use this repository for further follow-ups and contact me if you need access (I've granted access to you already).

sun’s picture

Status: Needs review » Reviewed & tested by the community

#20 looks good to me.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Excellent little clean-up. Committed to 8.x. Great job. :-)

Status: Fixed » Closed (fixed)

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

fago’s picture

This patch broke createDuplicate() implementations as we forgot to convert them. See #1547300: createDuplicate() is broken for most entity types and needs tests

fago’s picture

Issue summary: View changes

Updated issue summary.