Follow-up of #2382493: Population of default field values in entity translation is incorrect
Problem/Motivation
The current handling of default values in ContentEntityBase::addTranslation() feels very inefficient: it creates a new fresh entity that gets default values, only to be able to copy those values over to the new translation. Additionally hook_entity_create() is called when creating an entity translation which is incorrect.
Proposed resolution
- Make sure
ContentEntityStorageBase::doCreate($values)andContentEntityBase::addTranslation($values)reuse the same code for the "merge default values into the passed in $values". - Introduce a new
hook_entity_field_values_init()hook to allow to alter these values regardless of whether we are initializing a new entity or a new entity translation. - Introduce a new
hook_entity_translation_create()hook to allow to react to entity translation creation.
Remaining tasks
Validate the proposed solutionReviews- Evaluate whether it can be committed during the RC phase
User interface changes
None
API changes
Mostly additive:
ContentEntityStorageBasenow implements a newContentEntityStorageInterface.ContentEntityType::setStorageClass()throws an\InvalidArgumentExceptionif the entity storage handler does not implementContentEntityStorageInterface.- The
TranslatableInterface::isNewTranslation()method was added.
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #67 | diagram.pdf | 33.73 KB | alexpott |
| #63 | 2382675-2-63.patch | 24.16 KB | alexpott |
| #63 | 31-63-interdiff.txt | 7.54 KB | alexpott |
| #61 | drupalci-vagrant.log_.txt | 13.42 KB | plach |
| #56 | mysql_error.txt | 58.33 KB | hchonov |
Comments
Comment #1
yched commentedMaybe something like this ?
- ContentEntityStorageBase::doCreate($values) only takes care of applying the values that were passed in.
- default field values are applied by ContentEntityBase::postCreate() and ContentEntityBase::addTranslation()
Comment #3
yched commentedOops, wrong git flow, don't mind the patch in #1.
Here is the correct patch.
Comment #5
yched commentedDamn - the test fail is because $field->isEmpty() cannot be trusted until we fix #2164601: Stop auto-creating FieldItems on mere reading of $entity->field[N] :-/
Comment #6
yched commentedWell, so much for reusing then (we don't receive $values in Entity::postCreate()).
Different approach, 4 lines of code are duplicated between ContenetEntityBase::addTranslation() and ContentEntityStorageBase::doCreate(), no biggie.
Comment #7
plachComment #8
plachDuring the last #drupal-entity IRC meeting we agreed to introduce a new hook to be called in addition to
hook_entity_create()to allow to manipulate field default values without having to differentiate between entity creation and translation creation. The rationale is that there is no difference in initializing an entity object or a translation object wrt initial/default values. Suggested signatures:hook_entity_field_default_values_alter($values, $langcode)hook_entity_field_values_init($values, $langcode)(others?)
Comment #9
yched commented#2090983: ContentEntityInterface::getTranslation() should throw an exception when an invalid language is specified is part of that problem space
Comment #10
yched commentedUpdating title to more accurately reflect the issue at hand
Comment #11
fagoYep - the wrong hook being fired is the main issue that we have to fix here imo. Bumping priority for that.
Comment #12
plachComment #15
catchComment #16
catchWe need to decide if simply not invoking a hook, from the wrong place, is an API break.
If you rely on that behaviour, your module might break, but the behaviour is not supposed to happen at all in the first place.
Comment #17
plachYep, form my POV it is not an API break, it's a plain bug fix since the intended behavior would be preserved by introducing a new hook and the buggy behavior would be removed.
Comment #18
plachWorking on this
Comment #19
plachFirst attempt at implementing #8.
Comment #20
plachSpoke with @Berdir, this patch addresses also the related issue pointed out in https://github.com/md-systems/pathauto/issues/90#issuecomment-154173677. We agreed that a single issue makes sense in this case, since we are basically completing the Entity Translation API wrt entity translation creation.
Reviews welcome :)
Comment #21
plachUnrelated
Comment #22
penyaskitoFixed typo in tags
Comment #23
plachRerolled, reviews welcome...
Comment #24
hchonovWhy do we have to check here if the storage for the content entity implements the ContentEntityStorageInterace and do not instead enforce that content entity types define a storage class implementing the ContentEntityStorageInterace? We do exactly this in ConfigEntityType::__construct, where we enforce the defined storage class to be an instance of ConfigEntityStorage.
Comment #25
plachThat's a good suggestion, but I'm not sure we can still do that.
Comment #26
hchonovI guess your concerns are that if we do the check in the ContentEntityType::__construct then there will be a greater impact, but actually no matter where we do it there is going to be an impact. Doing it in the ContentEntityBase::addTranslation would mean we are going anyway to force anyone having a custom storage not extending from ContentEntityStorageBase to implement the new interface as soon as they wann create new translations. And actually doing this check in ContentEntityBase::addTranslation will hide the change for a longer time.
So I think that if we are going to do this change then we have to do it the right way even if there is a disruption.
Comment #27
plachImplemented #24, let's see whether the bot is happy.
Comment #28
catchThis looks great to me, and the sort of change we can't make in a patch release, so I'd support trying to get it in before 8.0.0.
I think we could make this change in 8.1.x, but there's more chance of modules relying on the current, buggy, behaviour by then than if we do it now.
Comment #30
hchonovSo, I guess we need a separate key value storage for content entities as well...
Comment #31
plachRrright
Comment #32
plachComment #33
hchonovMostly additive changes, a dedicated interface for content entity storage and a new hook_entity_field_values_init() hook to unify entity_create and translation_create. Nice work.
Everything looks good to me.
Comment #34
plachThanks!
Added two change records:
https://www.drupal.org/node/2616318
https://www.drupal.org/node/2616322
Comment #36
catchDiscussed with xjm and alexpott. Did't quite finish conversation with xjm, but alexpott agreed with getting this in before 8.0.0l on the basis the chance of disruption gets higher the longer core is doing the wrong thing,
Committed/pushed to 8.0.x, thanks!
Comment #37
plachThanks, published the change records!
Comment #38
alexpottI'm reverting this because it seems to be causing random fails see #2616808: Slew of random test failures with UpdatePathTestBase when using is_subclass_of() in ContentEntityType::checkStorageClass()
Comment #40
plachDouble checking...
Comment #46
hchonovI've just executed locally 15 times the following without a single failure:
php core/scripts/run-tests.sh --url "http://dev-d8.nauts.eu/" --class "\Drupal\system\Tests\Update\AutomatedCronUpdateWithAutomatedCronTest"And this is the test, which is failing more than once on the test bot.....
Comment #47
hchonovAn interessting fact as well is that all the tests that have failed on the test bot are using the dump
drupal-8.bare.standard.php.gz.Comment #48
berdirOne thing that has caused problems in the past on testbot is that it runs many tests in parallel. If you want to replicate that behavior, try something like --repeat 20 --concurrency 5 or even higher numbers.
But something like this, which smells like a segfault is also always very system/version specific. Any chance that we can get at the apache/php logs of the failing containers somehow?
Comment #49
plachMaking some experiments in #2496337-78: [plach] Testing issue.
Comment #50
plachThis seems to work.
Comment #55
hchonovJust atm I've tested locally but I gave by mistake the wrong --url and suprise... I got exactly the same errors like on the testbot...
Any clue?
Comment #56
hchonovas requested by @plach in IRC providing logs when submitting wrong --url
Comment #57
alexpottI thought I was on to something but I was wrong. However looking at this patch I think think interface inheritance is wrong.
ContentEntityStorageInterface extends DynamicallyFieldableEntityStorageInterface? I don't see why that should be the case. Shouldn'tContentEntityStorageInterface extends EntityStorageInterfaceand thenContentEntityStorageBaseimplement bothContentEntityStorageInterfaceandDynamicallyFieldableEntityStorageInterface?Comment #58
plachI modeled it after ContentEntityInterface...
Comment #59
plachI set up a local DrupalCI environment with Vagrant but no luck, the tests pass even with the exact same PHP version (5.5.23) on Ubuntu Trusty. Attached you can find the log...
Comment #60
plachComment #61
plachThe log...
Comment #62
alexpott@plach so what this might be is something to do with APC and concurrency - we did have random fails before to do with Entity storage classes - see #731724-527: Convert comment settings into a field to make them work with CMI and non-node entities
Comment #63
alexpottHere's a patch that shuffles the interfaces as suggested by #57 and doesn't suffer from the obscure random fail - see #2496337-122: [plach] Testing issue
Comment #64
plachThe interdiff looks good to me, RTBC +1.
Comment #65
plachChanges are quite simple so I think it's ok for me to move this back to RTBC.
Comment #66
hchonov+1 RTBC
Comment #67
alexpottHere's the class diagram of everything that implements EntityStorageInterface on applying the patch. Note how the ContentEntityStorageInterface and ConfigEntityStorageInterface nicely imitate each other now. It looks correct to me.
Comment #68
effulgentsia commentedI discussed this with the other committers, and we decided to allow this into 8.0.0, because:
So, tagging, and also recategorizing as a bug and retitling accordingly.
Comment #70
effulgentsia commentedI pushed #63 to 8.0.x. However, the following nits could use a follow-up issue/patch:
Doc doesn't match implementation.
What should be to-done?
Comment #71
effulgentsia commentedI also think it would be great to have a follow-up issue to add an integration test for what happens when hook_entity_create(), hook_entity_field_values_init(), and hook_entity_translation_create() are all implemented: i.e., that hook_entity_field_values_init() affects both entities and translations, and that the other two hooks only affect the corresponding one. Such a test should also test the expected order in which those hooks fire (i.e., hook_entity_field_values_init() before *_create()).
Comment #72
plachThanks!
I'm creating the follow-ups.
Comment #73
plachComment #75
gábor hojtsy