Field API instance CMI data include an entry for "default value".
This currently includes a 'target_id' as a numeric ID, which is totally deployment-unfriendly.
After #2056405: Let field types provide their support for "default values" and associated input UI and #2050801: Unify handling of default values between base and configurable fields field types can now massage default values before saving to & after loading from CMI, by overriding ConfigField::defaultValuesFormSubmit() & ConfigField::getDefaultValue().
entity_ref and taxo field types should use these to switch in UUIDs in a target_uuid entry, instead of numeric ids.
(filing this for entity_ref component, but both modules can probably be dealt with in the same patch)
Comment | File | Size | Author |
---|---|---|---|
#54 | default_values_uuids-2090541-54.patch | 18.97 KB | plopesc |
#54 | interdiff.txt | 1.5 KB | plopesc |
#52 | default_values_uuids-2090541-52.patch | 18.81 KB | plopesc |
#52 | interdiff.txt | 3.44 KB | plopesc |
#50 | default_values_uuids-2090541-50.patch | 18.94 KB | plopesc |
Comments
Comment #1
larowlanI wonder about {comment}.nid and uid cols in eg node data
Comment #2
yched CreditAttribution: yched commented@larowlan : that's nothing that gets exported in CMI files ?
Comment #3
plopescHello
Attaching a first approach for this issue. I found that there are entities that does not have uuid, like users 0 and 1 (Anonymous and admin). Then this patch check it and stores default values in the old w if the entity does not have uuid.
Suggestions are welcomed!
Regards
Comment #4
yched CreditAttribution: yched commentedThanks @plopesc! That's totally the idea.
Review below.
one empty line too many
Missing a phpdoc
I think we should start with $default_value = parent::getDefaultValue(), then iterate and replace uuids if we find some.
Also note: core standardizes on capital letters in comments : UUID, UUIDs (comments only, not property names or variable names)
since the original property is 'target_id', I'd vote for 'target_uuid' ?
is there a way to do a multiple-load ? it *seems* StorageController::loadByProperties(array('uuid' => array(several uuids)); will load all the entities in one single multiple-load.
'revision_id' is more complex, we cannot just ditch it on submit and use the "current revision" of the entity associated to the uuid on load.
However, revisions don't have UUIDs, so I'm not sure how we can support referencing a specific revision in entity_reference field...
Similarly, this should be parent::defaultValuesFormSubmit()
This should use entity_load_multiple(), though - and preferably, \Drupal->entityManager()->getStorageController($target_type)->loadMultiple();
First, iterate on values to collect ids, then load the entities, then iterate again to assign uuids from the loaded entities.
Minor: missing empty line after the last method of a class
should not happen :-)
Comment #5
amateescu CreditAttribution: amateescu commentedApart form @yched's excellent review, this file path bugs me a little. I don't think this should live under the field_type plugin namespace. I know FileField currently does, but I don't like it there either :)
Comment #6
plopescHello.
Attaching patch that addresses all points in #4 except 5 and 6, I think. Here my comments about thse points:
5.- Multiple load in
getDefaultValue()
makes code a bit weird IMHO. Maybe it loses consistency because it depends onloadByProperties()
does not change the entities order.6.- In cases of revisions, we could kept the target_id/revision_id pattern, in order to maintain the reference to a specific revision. It is the same approach we are following for non UUID entities.
I think we could split the code in
getDefaultValue()
anddefaultValuesFormSubmit()
to try to keep it cleaner, what do you think?About #5: @amateescu, I'm following the same approach that FileField. Mabe we could open a follow up issue to decide where put these classes. Or if you suggest a a better place, I have no problem to move there
ConfigurableEntityReferenceField
:)Regards.
Comment #7
yched CreditAttribution: yched commentedThanks for being so quick ;-)
Use $delta rather than $index, for consistency with the rest of core.
$item might be misleading, since, *unlike* everywhere else in core, this is an array and not a Field (list of FieldItem's) object.
Maybe $properties ?
If you key $uuids by delta, you shouldn't need the separate $indexes array.
Matter of taste, but I'd rather wrap the rest of the code in
if ($uuids) {
rather than exit early. Exit at the end, less code paths to figure out mentally.I don't think we can rely on the order of the $entities array returned by loadByProperties(), the current implementation might preserve the order, but it's not documented anywhere and thus could change at any given time.
So IMO we need to iterate on entities first, to build an $ids array of (uuid => id). Then,
same as above, $delta => $properties
misleading var name - $ids instead of $items
Minor, but the intermediate $entity variable could go away, $uuid = $entities[$item['target_id']]->uuid() is fine.
Also, the if(isset($uuid)) could use a comment about some entities not having UUIDs (and why it's not an issue - if that's not an issue ?)
Comment #8
yched CreditAttribution: yched commentedWe can streamline it a bit (see remarks above) - and multiple loading is fairly important for performance, not doing it when we can could qualify as a bug.
Let's see how the final code looks like, but I don't think that should be needed. Let's avoid cluttering the class with a separate helper method unless we need to.
Comment #9
plopescAddressing suggestions in #7.
Yeah, I know :) I was talking about the code readability. I think it is now better after changes suggested in #7
Now, we should decide how to manage the revision_id issue (I suggested a possible approach in #6) and the best place for the
ConfigurableEntityReferenceField
class.Regards.
Comment #11
plopesc#9: default_values_uuids-2090541-9.patch queued for re-testing.
Comment #12
yched CreditAttribution: yched commentedFirst comment is not really needed, The second one could be moved above the if(). Also, "process" is vague &nd doesn't add much. Just "Convert UUIDs to numeric IDs." ?
Leave one empty line after the parent call, like you do in the other method ? (consistency ++)
"Convert numeric IDs to UUIDs to ensure config deployability" ?
This should be left as a @todo then :-).
And we need to solve that. What were those cases of entities not having a UUID ?
+ language : s/does/do
Actually I don't think we should overwrite the value completely, just set 'target_uuid' and unset 'target_id', leaving the rest untouched, whatever that may be now or in the future - and same in getDefaultValue(). The job here is UUID <-> ID, nothing more.
Regarding revision_id, I think the current UI doesn't let you select a specific revision anyway, so the revision_id entry will be empty. Let's just not care about that. How e_r can reference a specific revision is not fully clear to me, and this can be adressed in a followup.
Regarding namespace / folder for the Field class, I do think it's generally preferable to keep Field classes next to the FieldItem classes, both are really related. Putting non plugin classes in plugin discovery folders is fine, that's what we do with base classes for widgets and formatters. Sensible code organization beats minor overhead on plugin cache miss IMO.
Comment #13
plopescSorry for the grammar errors... ;)
Re-rolling fixing comments and changing only UUID <->ID instead of the whole default_value array.
Regards.
Comment #14
yched CreditAttribution: yched commentedSo, regarding users 0 and 1:
I opened #2092603: Users 0 and 1 have no UUID about the missing UUIDs. Meanwhile, we could special case here and use 'anonymous' resp. 'administrator' as 'target_uuid' strings for user 0 resp. 1 ? (only if target_type is user of course) - with a @todo to clean this up after that other issue is fixed. Will make the methods a bit more convoluted, but that should be temporary...
I'm not actually sure entity_ref allows referencing the anonymous user, BTW. @amateescu ?
Also, we should add tests:
- Create an entity_ref field / instance through Field UI, enter a default value in the form (necessarily with a numeric id), check that the CMI record contains a UUID
- field_info_cache_clear()
- Create a fresh entity (with entity_create()), and check that it correctly receives the default value (field has the expected numeric target_id)
Comment #15
amateescu CreditAttribution: amateescu commentedYes, it does :)
And you're right that we don't need to worry about revision_id here, the support for it exists only in the schema and some internal code, but it's not exposed in the UI at all. It will definitely need more attention at some point.
Comment #16
yched CreditAttribution: yched commented@amateescu: ok - it's just that HEAD gives me an "Integrity constraint violation: 1062 Duplicate entry '' for key 'name': INSERT INTO {users}" fatal when I try to create a node referencing user 0 (using a select widget) :-). Is it a known bug or should I open an issue for that ?
Comment #17
BerdirMy guess is that Users need a different isNew() implementation, that returns FALSE when uid === 0.
Comment #18
plopescHello
I've been working in tests, but I'm not able to get it working. When I try to execute
field_info_cache_clear()
, it throws the folloeing exception:Drupal\Core\Entity\EntityStorageException: SQLSTATE[HY000]: General error: 1366 Incorrect integer value: '' for column 'field_entity_reference_revision_id' at row 1
However, if I skip the cache clear, when I create the new fresh entity, the entity_reference field default value is not set.
I reproduced the test steps manually and it works fine. I also tried to pass the same tests against 8.x branch and experienced the same annoying issue. (To test against 8.x branch, you should comment lines 87 - 89 regarding to target_uuid)
Any clue? I'm noob in D8 tests... and code is not already polished, just proof of concept.
Regards
Comment #20
plopescHello
After digging a bit more, I found that entities created through
entity_create()
does not include field default values... I've checked it with taests against entity_reference and text fields.Is this right? Is this a known bug?
Creating the new node through the UI, default values are set correctly and tests works now :)
Regards.
Comment #22
yched CreditAttribution: yched commentedentity_create() should absolutely initialize fields with their default values - and according to a quick test, this does work in today's HEAD.
Comment #23
larowlanI've done a lot of digging into how the applyDefaultValues stuff works over in #731724: Convert comment settings into a field to make them work with CMI and non-node entities, and if there is a default value in the instance settings, the applyDefaultValue function isn't called. Not sure if that helps.
Comment #24
yched CreditAttribution: yched commentedWell, on current HEAD:
- in Field UI, edit 'body' field instance on article nodes, and add a default value there
- then
entity_create('node', array('type' => 'article'))->get('body')->getValue();
does return the default values I defined, meaning they get correctly assigned on a fresh entity.Comment #25
plopescNew tests trying to demonstrate that it fails for programmatically created entities.
Some checks let me to think that this could be an issue related to testing environment.
Regards
Comment #27
plopescHello
After talking with @yched yesterday, submitting new patch cleaned and tests included. Also included temporary support for users 0 and 1 as suggested in #14 until #2050843: Users 0 and 1 are created without a UUID will have been fixed.
Now, I have a question... Should be necessary a hook_update_N implementation to convert previously values stored and target_id into target_uuid?
Regards.
Comment #28
yched CreditAttribution: yched commentedNo need for an upgrade path for entity_reference fields, since ER is a D8 addition.
But taxonomy_term_reference fields will need an upgrade function, yes.
Comment #29
plopescOK, I don't know if it could be necessary for sites which upgrade from old D8 releass like
8.x-alpha1
to the current version.About the
taxonomy_term_reference
fields, should we wait until #2015687: Convert field type to FieldType plugin for taxonomy module will be fixed?Currently
taxonomy_term_reference
still defined throughhook_field_info()
instead of using plugins. We could postpone this until thetaxonomy_term_reference
will be converted or cover onlyentity_reference
field in this issue and open another once #2015687: Convert field type to FieldType plugin for taxonomy module will be fixed.What do you think?
Regards.
Comment #30
plopescRe-rolling after #2051923: Rename \Drupal\Core\Entity\Field\Field, related subclasses and interfaces to *FieldItemList...
Any comment about questions in #29??
Regards.
Comment #31
amateescu CreditAttribution: amateescu commentedI think it's ok to wait for #2015687: Convert field type to FieldType plugin for taxonomy module . Also,
ConfigurableEntityReferenceField
needs to be renamed toConfigurableEntityReferenceFieldItemList
now ;)Comment #32
yched CreditAttribution: yched commentedAt this point in the D8 cycle, we don't provide upgrades for older D8 versions, only for D7
I don't think I see how #2015687: Convert field type to FieldType plugin for taxonomy module is related, or why it should postpone this issue ? Our case here is only about updating the content of field instance definitions that come from D7, and #2015687: Convert field type to FieldType plugin for taxonomy module will not change anything in this area.
taxonomy needs to provide a new update function, and make sure that it runs after field_update_8003() (using hook_update_dependencies() - look at how file_update_dependencies() does it)
The code should be something like this:
(@todo in the middle: write an SQL query to fetch the UUID of a given term_id)
[edit: simplified the code a bit]
(Note that taxonomy.module did not provide an update to rename 'tid' to target_id' in default values in $instance config so far, so the D7 upgrade was broken on that regard so far)
Comment #33
plopescHello
Added taxonomy support for default_value target_uuid and hook_update_N implementation that converts tids into target_uuid (as commented in #29 tid to target_id conversion was not done yet).
I've done some manual testing and it worked fine without any problem. However, my local testbot says "Fatal error: Class 'Drupal\entity_reference\Plugin\field\field_type\ConfigurableEntityReferenceFieldItemList' not found in /xxxx/yyyy/zzzz/drupal8/core/lib/Drupal/Core/TypedData/TypedDataManager.php on line 92"
Maybe something related to TypedDataConversion not yet ready?
Regards.
Comment #35
yched CreditAttribution: yched commentedGoing afk for the next two weeks, I'll let you guys drive this home :-)
Comment #36
plopescAfter some sleep, I found that the problem was that I used the same
ConfigurableEntityReferenceFieldItemList
as list_class for taxonomy_term_reference field. Then test fails because it creates a (maybe non desired) dependency on Entity Reference module.So, we can:
ConfigurableEntityReferenceFieldItemList
to other place in core where both modules can access to itAny thought?
New patch fixing bug in upgrade function.
Regards.
Comment #37
amateescu CreditAttribution: amateescu commentedAs sad as it may sound, I think we should go with copying the code for now and let #1847596: Remove Taxonomy term reference field in favor of Entity reference handle the unification.
Also, we need an upgrade path test for the taxonomy ref field.
Comment #38
plopescAfter more digging and testing, found that #2015687: Convert field type to FieldType plugin for taxonomy module is related to some test fails, given that taxonomy field works with
LegacyConfigFieldItemList
instead ofConfigFieldItemList
as default list_class.So, I created a new class in Taxonomy module named
TaxonomyTermReferenceFieldItemList
that extends fromLegacyConfigFieldItemList
where default values are managed.About the upgrade path test, which steps would check this test? Only the tid -> target_uuid conversion in default value?
If more steps should be tested, maybe would be better open a new follow-up issue and do it.
Regards
Comment #40
amateescu CreditAttribution: amateescu commentedYep, that should do it :)
Comment #41
plopescHello
New patch including upgrade path test for taxonomy_term default_value.
Comment #43
plopesc#41: default_values_uuids-2090541-41.patch queued for re-testing.
Comment #45
plopesc#41: default_values_uuids-2090541-41.patch queued for re-testing.
Comment #46
plopescRerolled after #2050835: Move Widget, Formatter, and FieldType plugin types to the Core\Field system is in.
I don't know why, but 'list_class' parameter is not taken into account if it is declared in hook_field_info(), however, if it's declared in hook_field_info_alter() it works like a charm. Any suggestion?
Comment #47
plopescRerolled after #2115145: Move field type plugins to Plugin/Field/FieldType.
I found the problem described in #46 about hook_field_info() and 'list_class'. This parameter was overriden in LegacyFieldDiscoveryDecorator::getDefinitions(), so only is possible override it in hook_field_info_alter().
I added this fix in this patch. If you consider it, we could open a follow-up issue where discuss about this.
Regards
Comment #48
yched CreditAttribution: yched commentedYay, the tests are awesome !
Minor remarks below.
Agreed with the workaround for 'list_class', but we should keep the other properties forced - just $definition += array('list_class' => ...);
Minor: we usually put full issues URLs rather that #1234 markers in comments. Also, this is not an exception (PHP sense) - "Remove this special case ..." ?
Missies a phpdoc
Minor: in code comments, refer to a node as "node" rather than as "content" (when it's a node...) - clearer :-)
Comment #49
yched CreditAttribution: yched commentedAlso:
We cannot exclude the fact that we might receive 'target_uuids' that do not exist on the site - $entity_ids[$uuid] won't be set.
In that case, we should unset($default_value[$delta]) - and re-number $default_value at the end to make sure we leave no wholes in deltas (just return array_values($default_value))
Comment #50
plopescRe-rolled including suggestions in #48and #49. Also implemented changes suggested in #49 in
TaxonomyTermReferenceFieldItemList
.Regards.
Comment #51
yched CreditAttribution: yched commentedMinor, phrasing: "Convert" rather than "Converting" ?
Do we really need the empty / !empty cases ?
Just something like
// Ensure we return sequential deltas, in case we had to remove unknown UUIDs.
$default_values = array_values($default_value);
should work ?
We try to keep those < 80 chars when possible.
Since we are in a class that tests entity_reference, maybe we can avoid repeating that here ? Something like: "Tests that default values are correctly translated to UUIDs in config." ?
After that, we should really be there ;)
Comment #52
plopesc@yched: About comment 2 in #51. I did it because in case all the uuids are not available, it would
return array();
. However, when there are not default values, itreturn NULL;
.I made some tests and it looks like there is no difference between NULL and array() for empty default value...
Regards.
Comment #53
yched CreditAttribution: yched commentedYes, returning an empty array is just fine, that's what happens for regular field types on a field instance where no default value is configured (the CMI file contains "default_value: empty array").
I'd just vote to add the comment suggested in #51 above the array_values() line, because it's not frankly intuitive otherwise.
(// Ensure we return sequential deltas, in case we had to remove unknown UUIDs.)
Minor, other than that, RTBC; Thanks !
Comment #54
plopescI used the following coment line to ensure we keep it < 80 chars:
"// Ensure we return consecutive deltas, in case we removed unknown UUIDs."
Regards.
Comment #54.0
plopescfoobar
Comment #55
catchI keep looking at this and thinking it'd be simpler to always store uuids for entity references, or both uuid and numeric ID, rather than doing the conversion. Is there an issue for that?
This isn't the place to change that so the conversion seems like the only option.
Comment #56
yched CreditAttribution: yched commentedAFAIK, storing UUIDs instead of numeric local IDs for entity ref field values has been discussed long ago in the entity_reference contrib queue, and dismissed as impacting perf (JOINs if joining on strings rather than ints ?)
Storing / accepting both on input: dunno is this has been discussed, but not sure it would really be simpler.
Comment #57
catchStrings on joins is only an issue if you're joining. While sometimes entity reference fields are used in WHERE queries (i.e. to find backreferences), often you're just loading the field values with the entity the field is attached to and the uuid/id isn't going to matter there.
Seems worth discussing. I'm not sure if here or a follow-up - if you strongly think it should be a follow-up let's open it and stick this back to RTBC.
Comment #58
amateescu CreditAttribution: amateescu commentedIt was also discussed a bit in the core queue, in the first few comments of #1757452: Support config entities in entity reference fields (that issue had a different title initially), and @fago seemed to be against it.
It would make *this* patch simpler, but it would complicate the code in ER itself, not really sure how much, so I can't say I'm against it.. it's worth a try.
We should also keep in mind that even without joins, when you want to load a bunch of entities by uuid, the system does an additional entity query to figure out the main ids. This wouldn't matter if we'd store both.
Comment #59
yched CreditAttribution: yched commentedJOINs are mostly leveraged by views relationships. But WHEREs would be impacted too, and they appear a lot with filters / contextual filters.
But yes, I think storing UUIDs is a whole discussion in itself. If/when this happens, the code added here will be easy to adapt/revert. Meanwhile, let's do this, coz config deployability is screwed without it.
Comment #60
yched CreditAttribution: yched commentedOpened #2129583: Consider storing UUIDs instead of / in addition to local ids for entity_reference fields ?
Comment #61
catchOK I think only both is going to be viable based on the past comments, and that's more or less denormalizing at that point which definitely needs its own discussion. Committed/pushed to 8x., thanks!
Comment #63
plopescCreated child issue #2162005: Clean up entity_reference default values once users 0 and 1 provides UUID once #2050843: Users 0 and 1 are created without a UUID is in.