It is not possible to react to an entity being duplicated

Created on 15 March 2019, about 5 years ago
Updated 13 May 2024, 19 days ago

Every entity has a createDuplicate() method, but that method blasts away the original ID without saving it anywhere.
This makes it impossible to react to an entity being duplicated.
For example, when duplicating a config entity bundle, we'd also like to duplicate related field instances and entity displays.

Furthermore, we're missing hooks. Right now core has hook_entity_create() at the beginning of an entity creation process, and hook_entity_insert() / hook_entity_update() at the end (when the new ID is known). Duplicate has neither.

Plan:

  1. Add a createDuplicate() method to Entity Storage, move the current logic there, call it from the entity class (just like we do for create()).
  2. Fire hook_entity_create_duplicate() and hook_ENTITY_TYPE_create_duplicate() from the storage method. This allows modules to respond to the initial entity being created.
  3. Add a getDuplicateSource() method to EntityInterface, along with a $duplicateSource property. Populate it in the storage method. The name is intentionally long to prevent conflicts with contrib (Commerce already has getSourceEntity() on an entity type, for example).
  4. Fire hook_entity_duplicate() and hook_ENTITY_TYPE_duplicate() from EntityStorageBase::doPostSave() . The argument is just the $entity . We document that getDuplicateSource() can be used to fetch the source.

Notes:
- Initial idea was getDuplicateSourceEntity() , shortened for brevity.

🐛 Bug report
Status

Needs work

Version

11.0 🔥

Component
Entity 

Last updated 1 day ago

Created by

🇷🇸Serbia bojanz

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Missing content requested by

🇦🇺Australia dpi
6 months ago
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @bojanz
  • 🇩🇪Germany hchonov 🇪🇺🇩🇪🇧🇬

    @bojanz, I've proposed something similar for tracking the revision_parent of duplicate entities in #2725523-30: Add a revision_parent field to revisionable entities :

    If for example we take a food magazine, then we create a duplicate of some recipe and want to offer different extended variants by the users, which create a duplicate and extend/modify the new recipe, but a link between the recipes is automatically created. This actually sounds like a really great out of the box feature for Umami and for presenting Drupal.
    I think that this feature might be used by a lot of projects and as such, it would be good to be part of the Drupal core.

    I think that in case of revisionable entity types the duplicate entity should reference its source through the new field "revision_parent". For non-revisionable entity types we could add a simply entity reference "parent" field and for config entities we could add a "source" property.

  • 🇷🇸Serbia bojanz

    It is a fair suggestion, but maybe we don't even need to store this. Just knowing the source/parent at the time of hook firing would be enough as a first step.

    Also note that Entity::create() currently calls the storage create() method, but createDuplicate() doesn't and no hook is fired.
    It might make sense to introduce a storage-level createDuplicate() which calls a hook_entity_create_duplicate().
    That would be a fairly non-controversial change, though it still doesn't solve the problem of generically knowing the source ID at save time.

  • 🇩🇪Germany hchonov 🇪🇺🇩🇪🇧🇬

    It is a fair suggestion, but maybe we don't even need to store this. Just knowing the source/parent at the time of hook firing would be enough as a first step.

    This will be for sure much easier to implement and maybe easier to get into core than what I am proposing and still makes it possible to add the persisting layer later :).

    Also introduce a hook_entity_duplicate(). This would allow us to pass $original_entity / $source_entity as a second argument if we feel like $entity->original is too much of a historical oddity.

    +1 on the hook, as it allows to duplicate nested referenced structures as well.

    I think it would be good to also have a new "source" property and the respective getSourceEntity() method on the EntityBase class, which we could access during the saving process.

    I am in favor of adding a new property and not reusing the "original" property, because while being similar those are different operations and it is assumed that "original" is not set during the saving process of new entities.

    It might make sense to introduce a storage-level createDuplicate() which calls a hook_entity_create_duplicate().

    Yes, this makes sense. We've done this for example with the createRevision method on the storage.

  • 🇷🇸Serbia bojanz

    You are right about $entity->original being too unclear in this case. Entities often have preSave() logic that compares ->original values, that logic would be unexpectedly triggered in case of a duplicate.

    We need a fresh property.

  • 🇷🇸Serbia bojanz

    Updating issue summary with the new plan, as thought-up with berdir and hchonov.

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    I wonder if the proposed getDuplicateSource() method might be a little too ambiguous of a name and could potentially result in accidental incorrect use, maybe getOriginatingEntity() and getOriginatingEntityId() (in the event you don't want to load the Entity and just want the ID)? To me that seems a little clearer that the entity which is being returned was the Entity that this Entity was based upon, but then as has previously been mentioned maybe that is out of the scope of this issue.

  • 🇨🇭Switzerland Berdir Switzerland

    I find getDuplicateSource() less ambiguous than your suggestions, especially because we already have $entity->original (which we want to deprecate in favor of methods) which is something entirely different.

  • 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 .

  • 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 .

  • 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 .

  • 🇩🇪Germany hchonov 🇪🇺🇩🇪🇧🇬

    In addition to #9 - getOriginatingEntity() is too general and sounds like there might be multiple origins, while getDuplicateSource() is more specific and semantically relates to createDuplicate().

  • First commit to issue fork.
  • @sakiland opened merge request.
  • @sakiland opened merge request.
  • 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 .

  • 🇷🇸Serbia sakiland

    I've created an initial quick solution without creating hook support and move logic into entity storage. I need this so I can fix issue with duplicating paragraph via Layout Builder #3190523: Paragraph is not cloned in Layout builder . This is just start point for this issue, but it's working solution.

    I've also crated two branches (one for 8.x and another for 9.x) and created two MR.

    If someone needs patch file to apply these changes via composer, near each MR there is 'plan diff' url to the appropriate patch.

  • 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 .

  • 🇩🇪Germany Anybody Porta Westfalica

    Just ran into a situation with layout_paragraphs where this also seems to be a blocker: 🐛 Cloning Entities containing Layout Paragraphs breaks structure Needs work at least for some cloning strategies. I'll add it to the related issues.

  • First commit to issue fork.
  • 🇺🇸United States richgerdes New Jersey, USA

    I've taken a pass at improvements here. I've updated the workflow for the createDuplicate() function to mirror that of create(), and making a call out to the storage service to handle the duplication. Similarly to the create() on the storage class, this implements a createDuplicate() invokes preCreateDuplicate() on the entity class, then calls doCreateDuplicate(), and ultimately calls $entity->postDuplicate() before invoking new hooks (ENTITY_TYPE_duplicate_create and entity_duplicate_create).

    I've opted for the name `duplicate_create` for the hook, which follows the same format as `revision_create`. Additionally new apis for getting/setting the duplicate source entity and checking if the entity is a duplicate (like isNew) are included now.

  • 🇺🇸United States richgerdes New Jersey, USA

    Sorry for the spam. I rebased the branches so that I could roll a patch that applies against 9.4.x, instead of 9.2.x, but forgot that I don't have access to change the MR target branch. Someone will need to update the target branch to 9.4.x so this can be better reviewed....

    In the mean time, I'm posting a patch here for reference. This should apply fine.

  • 🇫🇷France julien.sibi

    Hello @richgerdes,

    I was having errors with your patch when cloning a display mode on a order entity type.

    I got it working changing the following lines.

    In entityStorageBase :

    $duplicate->{$entity_type->getKey('id')} = NULL; 
    

    to

    $duplicate->set($entity_type->getKey('id'), NULL);
    
    $duplicate->{$entity_type->getKey('uuid')} = $this->uuidService->generate();
    

    to

    $duplicate->set($entity_type->getKey('uuid'), $this->uuidService->generate());
    

    then in configEntityBase :
    parent::postDuplicate();
    to
    parent::postDuplicate($storage);

    But I don't really measure any consequences of this patch, and I think it needs review before using in production..

    Julien

  • First commit to issue fork.
  • 🇺🇦Ukraine Taran2L Lviv

    So, tests are green:

    Changes since the #23:

    • Removed class property duplicateSource (as this is run-time information; also original property during update operation does not exist either)
    • Added uuid service to the base EntityStorageBase class
      • Created a deprecation + a test for it
      • Updated all children classes/related tests
  • Moved duplicate test into the correct place
  • 🇨🇭Switzerland Berdir Switzerland

    > @berdir could you elaborate why adding a new method is a BC break?

    Because it breaks implementations of that interface that implement it. We make exceptions in some cases, when we can safely assume that all implementations extend from a common base class, that is not really the case with EntityInterface, as for example the views module itself shows too

    > Not so sure about optional direction, I think the point of this update - to allow devs to act upon any entity being duplicated/cloned. Optional support will limit this significantly.

    My comment refers to the pre/post methods on the entity class, which would not hinder this in any way.

    But there are also the new method on the storage interface, which is also complicated and has the same problem as EntityInterface. Limited support is somewhat theoretical, if core base classes support it then that means 99% of all entities out there do.

    I didn't read through the whole issue, I guess the question is whether we _really_ need to apply the whole standard pattern here with storage methods. Invoking the hooks on the entity class directly would not be very pretty but it would be a trivial patch and would _not_ have to worry about all these BC concerns.

  • 🇺🇦Ukraine Taran2L Lviv

    @Berdir, thanks for your comments. I see your point here: the issue will be with code that uses the interfaces directly (without a base class).
    It's hard for me to measure the impact here, but in core it's only single ViewUI class.

    By the way, EntityStorageInterface has been recently updated and will require a change anyway, see #2570593: Allow entities to be subclassed using "bundle classes"

    Maybe more ideas from someone else ?

  • 🇺🇦Ukraine Taran2L Lviv

    So, I have checked contrib that extends/implements EntityInterface/EntityStorageInterface and most of them, like maybe 99%, then extending base classes. So, probably not a huge problem in the real world. However, some amount of modules would be forced to add a new UUID service parameter ... but the number is not that big from my research, maybe ~100, maybe less

    It's quite weird that ViewUI does that tbh, but it's not a proper entity, as it does not have annotations.

  • 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 .

  • 🇷🇸Serbia sakiland

    Thank you for your contribution on this issue,

    Definitely, we need an appropriate way to react on entity being cloned (duplicated). This is needed for the proper functionality of the core Layout builder. Here's the issue that I had with customizing the layout on the node level. It is related to the Entity Reference Revisions module, it doesn't react in the appropriate way when an entity is cloned, but it doesn't have a way how to react to this event. Here's some solution 🐛 New revision is not created via Layout Builder Active , but it depends on the method isDuplicate

    Also, each clone module has an issue with the paragraphs, and each fixes in a different way, but the core issue is in the Entity Reference Revisions module. For example:

    If you check the beginning of this issue

    Every entity has a createDuplicate() method, but that method blasts away the original ID without saving it anywhere.
    This makes it impossible to react to an entity being duplicated.

    so we need somewhere the reference to the original entity and also a way to check if an entity is a duplicate.

    EntityInterface contains the method isNew() and I think it should also contains the method isDuplicate. Or if you think this is BC, we can move this method into EntityBase

  • Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle .

  • First commit to issue fork.
  • @sakiland opened merge request.
  • @sakiland opened merge request.
  • 🇷🇸Serbia sakiland

    It's impossible to do rebase against 9.5.x on the existing branch `3040556-duplicate-entity-source` (also on the branch `3040556-duplicate-entity-source-9.5.x`), so I've created a new branch `3040556-duplicate-entity-source-fix-rebase`

    Let's wait weather test passing and I will do some branch clean up in the following days.
    Thank you @ksenzee for your branch `3040556-duplicate-entity-source-9.5.x`, otherwise I will lost some code (it's my mistake, my Phpstorm rebased and pushed code at once :( )

  • 🇷🇸Serbia sakiland

    I've fixed main branch 3040556-duplicate-entity-source. Now it contains valid code. Currently, MR 204 related to this branch, is created against 9.5.x; but after 9.5.x release date, I will move this to the 10.1.x

    @ksenzee after 9.5.x release date, please rebase your branch 3040556-duplicate-entity-source-9.5.x to the latest 9.5.x code, in order to have a valid MR (patch) for the 9.5.x code base.

    All other branches without open MR are not valid (these are deprecated or temporary branches, but I don't have options to delete them).

    Regarding failed test; I'm working on this, but I don't have an idea how to solve this.
    In the unit test Drupal\Tests\Core\Config\Entity\ConfigEntityBaseUnitTest there is configuration entity with random id. And for this entity, the storage is empty, and that's why test failed.

    $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
    
    /** @var \Drupal\Core\Entity\EntityDuplicateInterface $duplicate */
    $duplicate = $storage->createDuplicate($this);
    
  • 🇷🇸Serbia sakiland

    I fixed failed test via adding mocked storage service.

    In commets above I can see some issues with search_api module. Can you please check if the last code from branch 3040556-duplicate-entity-source produce this issue and give me more info regarding this?

    Thank you!

  • The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • 🇫🇮Finland jhuhta

    In commets above I can see some issues with search_api module. Can you please check if the last code from branch 3040556-duplicate-entity-source produce this issue and give me more info regarding this?

    I've been using the patch successfully on a busy production site since mid December on Drupal 9.4 and now also on Drupal 9.5. The site has also search_api enabled and in use.

  • Status changed to Needs review over 1 year ago
  • Status changed to Needs work over 1 year ago
  • The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • 🇫🇷France nod_ Lille

    If this is a 9.x patch it's best to change the version for this issue metadata, otherwise the bot will keep putting back to needs work.

  • Status changed to Needs review over 1 year ago
  • 🇫🇮Finland jhuhta

    True that.

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    I think the issue version should still be 10.1.x, the merge request just needs to be updated to 10.1.x. All development should be done against the latest version, and will be backported on commit if need be.

  • 🇷🇸Serbia sakiland

    Yes, that's right. Version should be 10.1.x
    I will change that next week, but first we need branch for 9.5

    @ksenzee after can you please rebase your branch 3040556-duplicate-entity-source-9.5.x to the latest 9.5.x code, in order to have a valid MR (patch) for the 9.5.x code base.

    After that I will change base version for main branch. Otherwise I will create a new branch for 9.5. But, I will prefer to keep as little as possible number of different branches if we don't need them.

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    @sakiland We should only need a single branch and merge request, targeted at 10.1.x, multiple merge requests is going to make reviewing this more challenging. When it comes to committing, the core committers will commit it to 10.1.x, something like this is unlikely to get backported to 10.0 nor 9.5.

  • 🇷🇸Serbia sakiland

    Yes, I follow you, but Drupal 9.5 is supported until Nov 2023. We need MR against 9.5 too. That's why we need a separate branch. And the main branch should be against 10.1 as you suggested. And reviewing should only be done on the main branch (I use this patch over two years from 8.9 to 9.5 without issue, and I think there are couple of people who use it too, so I don't think we need extra reviewing for old version branches)

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    @sakiland Like I said, if at the time of commit, the core committers feel that it makes sense to back-port this to 9.5 and 10.0, then they will do that by taking the 10.1 branch and back-porting it. Yes 9.5 is supported but it will only receive bug fixes and security fixes, all new development should be done only against 10.1.x, and so having a 9.5 branch here will only make it more challenging to review and get this to RTBC.

  • Status changed to Needs work over 1 year ago
  • 🇷🇸Serbia sakiland

    @aaronmchale Thank you for the suggestion, I appreciate it, and I understand what is your point. But, it seems you are missing the context of this issue. This issue is almost 4 years old, and there are couple of production sites already using this MR. These sites are probably still on the Drupal 9.5 (for my sites I'm sure they are on the 9.5 :), and it will be couple of next months)
    That's why we need MR against 9.5 branch, in order to not break existing sites already using this patch.

    Also, I don't expect that this patch will be back-ported to the 9.5, this is too much effort for nothing (sites with Drupal 9.5 will continue to work with this patch in the same way as in previous couple of years. so there is no need for back-port).
    I don't see any extra effort if we have separate MR for 9.5 branch. Core committer should only check the main branch (main MR) that will target 10.1.x (same approach when we have multiple patches on the issue, core committer will check only the last one that is tested by community). Yes, having multiple branches is not so clear when we have one branch and one MR, but I don't see any other solution for this kind of complications with multiple active versions (but, this out of scope for this issue, so for any other improvement regarding this, we can discuss on Slack :) )

    I will change status to need work until I make a valid MR to the 10.1

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    @sakiland Given what you've said, I would suggest updating a patch file for 9.5 rather than have a MR, that way as you say for sites which do need this there is a patch they can apply, but that doesn't then risk complicating the review process.

    So I recommend a MR against 10.1, and a 9.5 patch file for those who need it :)

  • 🇷🇸Serbia sakiland

    @aaronmchale Thank you for the recommendation. Ok, I will do as you suggested and create a patch file for 9.5
    At the end, most important is to make it easier review process :)

  • 🇬🇧United Kingdom AaronMcHale Edinburgh, Scotland

    Thanks @sakiland.

    I'm going to change the version back to 10.1.x in line with core policy for new features.

  • 🇷🇸Serbia sakiland

    Here's the patch for the 9.5.x branch.

    IMPORTANT: I will change the base branch for MR 204 to 10.1.x, so it will be no valid for the 9.5.

  • Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened , as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle .

  • 🇫🇮Finland jhuhta

    Rebased the 9.5.x branch and re-rolled the 9.5.x patch: applies now against 9.5.7.

  • 🇫🇮Finland jhuhta

    Re-applied the patch against 10.1.x and 11.x branches. Didn't have that much luck with GitLab so here we go traditionally with patch files. Feel free to test and review.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MariaDB 10.3.22
    last update 7 months ago
    29,658 pass, 2 fail
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MariaDB 10.3.22
    last update 7 months ago
    30,461 pass, 2 fail
  • Merge request !53263040556 - 11.x version → (Open) created by jhuhta
  • Pipeline finished with Failed
    7 months ago
    Total: 649s
    #47258
  • Pipeline finished with Failed
    7 months ago
    Total: 155s
    #47467
  • Pipeline finished with Success
    7 months ago
    Total: 613s
    #47471
  • 🇫🇮Finland jhuhta

    Here I removed a redundant test function, as the kernel test suffices. Got help with this from @lauriii.

  • Pipeline finished with Success
    7 months ago
    Total: 872s
    #47492
  • Status changed to Needs review 7 months ago
  • Issue was unassigned.
  • Status changed to Needs work 7 months ago
  • 🇨🇭Switzerland Berdir Switzerland

    I'm still not sure about this.

    Moving to the storage is consistent with other API's, but it adds considerable new API surface, new interfaces, constructor deprecations. It's also not clear now whether the storage or the entity methods are the actual API, because calling the storage directly would be missing the setDuplicateSource() call.

    I think this needs to be simplified if we ever want it to get committed. The minimal version to satisfy the issue title is a hook added to the existing createDuplicate() method. That hook can have two arguments entity and source. EntityBase and its subclasses can continue to override it.

    What is the use case for the getDuplicateSource() and related methods? What operation can only be done when the duplicate entity is being saved and still needs to know the original?

    There is also no new test coverage for the hooks yet, they should be implemented in a test module and do a modification that is asserted in a test.

  • 🇷🇸Serbia sakiland

    I started to work on this 3 years ago in order to fix issue with duplicate Entity reference 🐛 New revision is not created via Layout Builder Active

    The main check in that issue 🐛 New revision is not created via Layout Builder Active is the code
    if ($host->isNew() && $host->isDuplicate()) {

    And this line will fix all issues related to the cloning process in multiple contributed modules (I've already wrote about this one year ago).
    For that purpose we only need to know if entity is new one and also duplicate.

    As initial solution, I've implemented this method in \Drupal\Core\Entity\EntityBase (in the similar way as \Drupal\Core\Entity\EntityBase::isNew

    That was working solution, easy to test and maintain.

    Solution with hook is also ok if that can fix the issue with the related issue.
    Unfortunately, I don't have any live project related to this issue (client left the Drupal technology).

    So, for this issue the main task should be to define in which direction we want to go. Otherwise we will waste valuable developer's time, loosing clients due the the issue with loosing data, etc.

  • 🇬🇧United Kingdom joachim

    I agree with @Berdir that this is pretty complicated.

    Is the pre/postDuplicate stuff necessary?

    Also, in passing:

     * Due to the backward compatibility, we cannot put duplicate functionality
     * into the EntityInterface, and that's why this interface was introduced.
     * @see https://www.drupal.org/project/drupal/issues/3040556
    

    The BC policy says:

    > However, we reserve the ability to add methods to these interfaces in minor releases to support new features.

    So we can add functionality to EntityInterface if we want to. But it seems like a lot of new API.

  • Pipeline finished with Failed
    4 months ago
    Total: 172s
    #96896
  • Pipeline finished with Success
    4 months ago
    Total: 568s
    #96898
  • First commit to issue fork.
  • Is there a d10 version of this patch? We'd been using !2980 on a D9 site and are trying to upgrade to D10. !204 looks like its set for D10.1, but for some reason that MR has over a thousand commits, most of which don't seem related.

  • 🇺🇸United States richgerdes New Jersey, USA

    @Mmillford, #60 should have a D10 patch that hopefully works. Probably need to test it though. The !204 MR has been rebased and remapped from D9, so that's likely where the extra commits came from.

  • 🇺🇸United States richgerdes New Jersey, USA

    So we can add functionality to EntityInterface if we want to.

    While we can add new apis, it's better to not add BC breaks if we can avoid it. Similarly to the entity class, we are adding a function to the EntityStorageInterface too, which is also a BC break. We probably want to add an interface that add the create function. It would be nice to see these two new interfaces be removed in a future version of Drupal core (probably not 11 at this point, but 12 maybe?).

    Is the pre/postDuplicate stuff necessary?

    Sure, technically a hook is all that's needed to cover the basic description, but when I ran into this issue, i also wanted to check the duplicate state at save time, which isn't covered by just using a hook unless I create those properties myself during the hook.

    Both the config entity base and content entity base make use of the pre/post hooks. We could move that logic into the storage class, but I would recommend against it. If the api is setting an expectation for what will happen with an entity, then the logic should live on the entity class, even it its more functions. IMO the storage service should deal directly with loading entities, and not with managing entity properties field data. This is the best way to ensure that data is updated correctly over time.

    Moving this logic to the entity storage class is a bad idea because a developer could override the storage class with a custom implementation. While we hope that the class override extends the existing base storage, we can't be sure that it will and that means the new class might not include the same logic for updating the entity during these events and for the duplicate operation to work consistently, then entity class is the best place for the logic.

    The other question is what does core already do? The entity classes already have pre/postSave() and pre/postDelete() functions, so there is precedence to have a similar api structure for duplicate. Additionally, since the duplicate process is similar to the create process, which would call the entity's constructor, we should have a way for the entity class to react to that as well, no?

    On the flip side, there is a lot of logic in the existing content/config entity storage base classes as well. For example both createRevision() and createTranslation() exist on the ContentEntityStorageBase . Given the possibility of overriding the class the field value updates should probably be moved to the entity class instead, unless there is a strong need to tie the logic into the storage service such as loading the new serial id from the database. Given the current logic for these two functions, the main reason we need to use the storage handler for entity operations is to expose the module handler service in order to invoke info and alter hooks. If we want hooks, this may be unavoidable, but the hooks could be run along side.

    Given the above, I would still couple any entity data changes as closely to the entity class as possible, and for translation and revision support, that should likely live in the relevant traits instead of the generalized storage.

    I am for simplifying logic where possible. Right now during clone the following happens.

    1. User calls EntityBase::create()
    2. That gods to EntityStorageBase->create()
    3. We then call EntityBase::preCreate()
    4. Then EntityStorageBase->doCreate() which calls EntityBase::__construct()
    5. EntityStorageBase->create() then calls $entity->enforceIsNew()
    6. Folloed by
       $entity->post create() ?</li>
        <li>Finally the "entity_create" hook</li>
      </ol>
      
      This flow is pretty lean, and doesn't expose any pre/post operations on the storage class itself, so either we would need need to add support for pre/post functions there, or a developer would be forced to override <?php EntityStorageBase->create() 

      to alter the logic, which seems more messy then adding a few extra functions to the process ahead of time. Maybe its not necessary, but seems like its a better Developer experience to provide the apis up front, which is what I think this patch is currently doing to mimic the create flow.

      What is the use case for the getDuplicateSource() and related methods?

      I think the duplicate source is a valid thing to keep track of during the duplication process. Sure this could just be a variable passed to the classes, but I think it makes sense to store it on the entity level as a temporary value, in order for use of the "duplicate" state outside of the hooks. For example would be nice if a module wanted to change something in the form during load, based on the duplicate state, there is no way for it to identify that without the class property/function.

      What's left?

      Skimming the comments, it seems like the last major issue outside of the above discussion is that "there is also no new test coverage for the hooks yet". Is there anything else that still needs to be done here?

  • 🇺🇸United States richgerdes New Jersey, USA

    Updated description formatting.

  • Production build 0.69.0 2024