entity_create('some_string', $array) is pretty cumbersome to use, especially for all the test field entities that are created.

In the Entity Field API DX discussions, we agreed that adding static create would help.

Advantage:
- Can be documented with the correct @return Interface, so autocomplete works
- You don't need to remember the entity type string
- You don't have to call out to a function (you still have to call a static method, but that's already better and I think is fine in e.g. tests and procedural code)

Disadvantage:
- A few lines of code and a docblock to copy
- It could be a bit weird if someone replaces the used implementation and you'd get a different instance back than the static class you called (but I don't think this will happen often and if, will very likely be a subclass)

Questions:
Always use create(array $values) or have separate arguments for always required arguments, config entities e.g. could always include the id.

Comments

tim.plunkett’s picture

Entity types are theoretically swappable. You could swap the 'node' entity type out for a new class. If we start using Node::create(), that will fall through.

chx’s picture

Indeed sometimes you want to denormalize something right into the Node table and do something with it; but one would need to review the node class whether you want to replace the Node for that or just NodeStorageController.

fago’s picture

If we start using Node::create(), that will fall through.

During the discussion we figured this should not be a problem, as Node::create() then would just create an instance of the overridden class as a node_create() helper would do. As it's documented that it would return NodeInterface that should be fine as long as people fulfill that. (and if they don't, it's there fault)

chx’s picture

Fair enough. new static I guess.

msonnabaum’s picture

Status: Active » Needs review
StatusFileSize
new10.3 KB

Here's a quick stab at how this could work. It shows an example of when entities would need to explicitly supply their name, but that should be the exception.

One problem I ran into on this however, is that the static entityManager property ends up in the config entities yaml file. No idea what's going on there, but it looks like we're doing some kind of introspection of properties to come up with that? Seems super fragile…

tim.plunkett’s picture

Title: Add $EntityType::create() to simplify creating new issues » Add $EntityType::create() to simplify creating new entities

?

msonnabaum’s picture

StatusFileSize
new10.95 KB

This should fix the bug I described in #5.

Status: Needs review » Needs work
Issue tags: -Entity Field API

The last submitted patch, entity-create-2096899-7.patch, failed testing.

berdir’s picture

Status: Needs work » Needs review
Issue tags: +Entity Field API

#7: entity-create-2096899-7.patch queued for re-testing.

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -59,6 +61,37 @@ class Entity implements \IteratorAggregate, EntityInterface {
+   * @return Drupal\Core\Entity\EntityInterface
+   *   A new entity object.
+   */
+  static function create(array $values) {
+    $entity_type = static::entityName();
+    return static::manager()
+      ->getStorageController($entity_type)
+      ->create($values);
+  }

As fancy as this is, it partially defeats one of the reasons for adding this.. having a useful @return hint for the specific entity type.

Not sure...

fago’s picture

I think the document @return is quite a win and worth defining it on each of the entity classes. Shouldn't be a big deal, is it?

msonnabaum’s picture

I thought the idea was that we'd either use the @method phpdoc and not override, or override and just call parent? I dont see a reason to implement any logic on a per entity basis there.

plach’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Entity.php
    @@ -23,6 +23,8 @@
    +  static $entityManager;
    

    Is this public so that we can reset it?

  2. +++ b/core/lib/Drupal/Core/Entity/Entity.php
    @@ -59,6 +61,37 @@ class Entity implements \IteratorAggregate, EntityInterface {
    +  static function entityName() {
    

    I find this method a bit confusing: would it be so bad if we renamed it to Entity::entityType() and dropped the non-static one?

fago’s picture

I thought the idea was that we'd either use the @method phpdoc and not override, or override and just call parent? I dont see a reason to implement any logic on a per entity basis there.

Yeah, as long as the entity-type specific return interface is documented I'm happy. But I do not see that in the patch?

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -59,6 +61,37 @@ class Entity implements \IteratorAggregate, EntityInterface {
+  static function manager() {
+    if (!static::$entityManager) {
+      static::$entityManager = \Drupal::entityManager();
+    }
+    return static::$entityManager;

I'm not sure this is really worth another static?

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -59,6 +61,37 @@ class Entity implements \IteratorAggregate, EntityInterface {
+  static function entityName() {
+    $parts = explode('\\', get_called_class());
+    return \Drupal::underscore(end($parts));

This should by entityType(), but that would clash with the non-static variant I guess?

fago’s picture

Issue summary: View changes

Updated issue summary.

berdir’s picture

7: entity-create-2096899-7.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 7: entity-create-2096899-7.patch, failed testing.

yched’s picture

How would this play with the fact that runtime code isn't suppose to hardcode any knowledge of the actual class for an entity type, which could be swapped ?
That's the whole point of the "entity type string name" indirection (talk about 'node' rather than Node)

yched’s picture

berdir’s picture

That's handled, currently with some magic based on the class name that can be overridden if it doesn't match. It's just a shortcut, it works exactly the same as entity_create() and uses whatever factory etc. that uses.

So, the factory issue doesn't change anything apart from having multiple static methods, one that should be called directly and the other not.

xjm’s picture

Priority: Normal » Major
Issue summary: View changes
Issue tags: +beta target
martin107’s picture

Issue tags: +Needs reroll
xjm’s picture

webchick’s picture

Can we get some clarification on why this is postponed on that one? Seems like the two could easily happen in parallel and whichever's ready first wins.

The reason I ask is there are starting to be a bunch of patches that speed up tests (yay!), but as a result they make the tests 100x more verbose and complicated (as an example, see #2254183: Fix test performance of Drupal\filter\Tests\FilterAdminTest, which Dries rightly pushes back on for this reason). If instead this logic lived in FilterFormat::create(), for example, this would not only help test authors but also distribution authors and everyone else.

berdir’s picture

This is postponed because all the complexity in this two patches is exactly the same. The other issue is pretty much ready, has extensive test coverage and just needs some final reviews and an RTBC.

Once it is in, then the essential part of this will be 3 trivial lines of code.

As discussed in the other issue, this will *not* make that issue and similar ones easier, all it will do is change entity_create('filter_format', ...) to FilterFormat::create(...), everything else will stay the same, the complexity that you see there is data the filter format data structures, not the API. I fully agree with @sun and @timplunkett over there that it makes the test less brittle and more explicit about what it is testing instead of relying on some default configuration somewhere.

amateescu’s picture

Status: Postponed » Needs review
Issue tags: -Needs reroll
StatusFileSize
new1.5 KB
berdir’s picture

Great. Possibly add a single unit test for create() ? ( we don't need all the variations that we added for load(), that code is already tested).

There are no wrappers for entity_create(), so nothing to deprecate, we can update the change records after it was committed...

Only thing that we could do is pick a module and update it to use this, just like we did with load(), to see it working outside of unit tests as well.

Status: Needs review » Needs work

The last submitted patch, 25: 2096899-25.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new6.46 KB
new4.96 KB

Sure, would this be enough?

berdir’s picture

Status: Needs review » Reviewed & tested by the community

I think it is, yes :)

As mentioned above, I think we can extend the change record created for #2190313: Add $EntityType::load() and loadMultiple() to simplify loading entities, I'll also go through existing change records and update them to use the new method, just like I did there.

fago’s picture

That's a good addition and goes inline with #2190313: Add $EntityType::load() and loadMultiple() to simplify loading entities. Patch looks fine as well, RTBC+1.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Awesome, this patch makes me very happy from a DX perspective! :) And is a good complement to ::load().

Unfortunately, Tim's concerns are still unresolved. Though he did mention in IRC that #2028097: Map data types to interfaces to make typed data discoverable might help, and also acknowledged that this concern affects a very small minority of contrib. I think if this turns out to be a real limitation in a contrib module we can revisit at that time.

Committed and pushed to 8.x. Thanks!

  • Commit 6131ff1 on 8.x by webchick:
    Issue #2096899 by amateescu, msonnabaum | Berdir: Add ::create() to...
berdir’s picture

Status: Fixed » Closed (fixed)

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