Motivation
- Getting the constraint that are applied to a certain data or field definition is confusing and not easy, as $definition->getConstraints() only returns the explicitly specified ones. Right now, it's confusing on where and how type-based defaults are added in.
- $definition->getClass() does not return the data type class which ends up being used, however that is what you usually need.
- Listdefinition::getClass() already adds in the default class based on the list item type, so it's not consistent on how defaults are added in right now
Proposed resolution
Always apply defaults for getClass() and getConstraints() and document that.
Remaining tasks
-
User interface changes
-
API changes
- DataDefinition::getConstraints(), DataDefinition::getConstraint() as well as their FieldDefinition equivalences add in type based defaults
- DataDefinition::getClass() adds in the type based default
Original report by fago
Spin-off from #2047229: Make use of classes for entity field and data definitions. This issue creates classed definition objects which are used for defining fields and properties. Now, as we have classed definition objects the question comes whether they definition objects should be dumb and just return what has been set on them, or be smart and apply defaults (which might not be static but stem from data type's plugin defintion).
Example, data definition's have an optional 'class' property which may be set to override the data type's plugin class. Thus, if the class is not set, should $definition->getClass() return NULL or the data type's (default) class?
E.g. we could do
public function getClass($apply_default = TRUE) {
// Get manager from container if not supplied.
// Fetch type default from manager if no class is set.
}
If we decide to apply defaults the question comes up on how to get the manager best. I'd suggest doing a static helper method that reads from a public static variable on the class, what allows one to easily inject a manager for the whole class during unit-testing, while definitions continue to have no dependencies and can be easily serialized (what is critical!).
public static function typedData() {
if (!isset(static::$typedData)) {
static::$typedData = \Drupal::typedData();
}
return static::$typedData;
}
We've a similar situation as for 'class' with constraints. Right now we've
$definition->getConstraints(); // Gets any customly set constraints
$data->getConstraints(); // Short cut to the next
$manager->getConstraints($typed_data); // Applies defaults constraints and instantiates constraint objects
So, regardless of how we decide to move on with this issue we need to improve the confusion around those getCosntraint() methods, but depending on this issue we'll need a different solution - so let's clarify this first!
Comment | File | Size | Author |
---|---|---|---|
#28 | d8_definition_defaults.interdiff.txt | 1.14 KB | fago |
#28 | d8_definition_defaults.patch | 16.28 KB | fago |
#26 | typeddata-2116341-26.patch | 16.46 KB | tim.plunkett |
#20 | d8_definition_defaults.interdiff.txt | 6.88 KB | fago |
#20 | d8_definition_defaults.patch | 16.67 KB | fago |
Comments
Comment #1
yched CreditAttribution: yched commentedHaving definitions be the go-to objects for asking about information sounds like a nice idea in principle. That's basically what FieldDefinitionInterface::getSchema() is doing.
Some thoughts though:
- "Smart logic" like "merging from an outside source" raises the question of statically caching the results, meaning we then need to be cautious about serialization.
- Getters that are not symmetrical with setters feel a bit slippery API-wise. Means get(set()) is not stricly idempotent. You can only "get()" the merged version, not "what the caller of set() just placed in there". Shaky for stuff that ends up saved in Field / Instance config entities (you save the merged version, not strictly "the definition").
getSchema() escapes that, since that's purely derived data, with no associated setter.
- Having multiple potential sources for the info is indeed confusing. "Who should I ask then ? Who has which version ?" - e.g for constraints:
$data->getConstraints() ?
$definition->getConstraints() ?
$manager->getConstraints($data) ?
It's probably a bit clearer if $data::getConstraints() is a static - at least shows you're not supposed to call it through a $data object (although I guess it still shows up in IDEs autocomplete for "$data->" ?)
But right, settling on a pattern of :
static FieldItem::schema($field_definition)
static FieldItem::getPropertyDefinitions($field_definition)
static FieldItem::getConstraints($field_definition)
would make sense - although, er, would that mean schema() should be getSchema() ?
- We probably can't apply that pattern everywhere.
FieldDefinition::getDisplayOptions(), added in #2144919: Allow widgets and formatters for base fields to be configured in Field UI, for example: it only returns "striclty the defaults that were defined in setDisplayOptions()", not the actual resolved runtime display options, those are held in an EntityDisplay, we probably want to add EntityDisplays to the mix here :-)
Would it help if we made a list of "things" we'd want to apply that pattern to:
- class
- constraints
- schema
- ?
Comment #2
fagoBased on #2175017: FieldDefinition::create() doesn't populate default 'settings' for the field type and recent discussions it appears that we want to do that.
I've been discussing with yched possible implementation approaches in IRC a bit. We mostly discussed two ways:
a) set defaults during object construction, then have usual get()/set()
b) apply defaults in the getters, but optional. e.g. getConstraints($apply_defaults = TRUE)
a) seems good to me in general, but has two problems:
- some defaults depend on other field definition settings, in particular constraints. Imo it would be nice when $definition->getConstraints() just gives you all the defaults, but that approach would not work when setting them during construction
- I fear setting all the default constraints will eat too much memory - as it will sit in all the serialized field definitions in memory
Comment #3
fagoSo we figured we could regularly do a), while we handle constraints different and go with b) there.
Comment #4
tim.plunkettSee #2272161: Entity-based conditions should use type => entity:$entitytype instead of constraints for a), I think that can go in on its own, and we can handle b) here...?
Comment #5
fagoSo a) is problematic due to memory issues for field definitions and having type-based constraints being alterable via the data definition, what shouldn't be the case. Therefor, I think we should go with variant b) here as mentioned in #3.
Comment #6
tim.plunkettOkay, b) should work for my needs.
Comment #7
tim.plunkettHere we are. This test should illustrate why I had to postpone #2272161: Entity-based conditions should use type => entity:$entitytype instead of constraints.
Comment #10
fagoThat's wrong. DataDefinition is not the right class for all kind of types, in particular not for entities. use $typed_data_manager->createDataDefinition() and "entity:node" will give you all the wanted constraints.
Comment #11
fagoI figured the problem of #10 is in the Context class also. Problem is for fixing that we need an easy way to set the array-of-definition values on the resulting definition class - we do not have that right now. Not sure, it's reasonable to try to fix that though - shouldn't we use definition classes for context definitions in the first place? -> Let's discuss this in its own issue?
@Applying defaults to definition classes: I think we should default to applying defaults, as this is usually what you want. Also I moved all existing defaults from the manager into it and fixed the confusion with the naming of TypedDataManager::getConstraints() - updated patch attached.
Comment #12
fagoFixed some docu glitches.
Comment #15
tim.plunkettThat's fine, but we need some form of test here, so that we can safely make the change in #2272161: Entity-based conditions should use type => entity:$entitytype instead of constraints
Comment #16
fagoad #15: ok, so let's extend coverage over there?
Comment #17
tim.plunkettOkay, sounds good.
I avoided making the default TRUE because I didn't want to track down all of those instances, but its definitely better.
Comment #18
tstoecklerComing here from #2236903: setSettings() on field definitions can unintentionally clear default values.
The issue summary does not at all clarify the use-case for setting
$apply_defaults
to FALSE. I.e. why this is needed in the first place. The boilerplate documentation in the patch does not reveal any information either, as to when I would want to use which. Marking needs work for that at least.The confusing-ness of the different getConstraints() methods is a thing, but I'm not sure this is the solution. I.e. when I have a data definition and I want to get a list of constraints why would I only want those specifically set up for this particular field definition. I want all constraints that are applicable to this field definition. If I don't want the defaults to apply, I can remove them with setConstraints() (or if that doesn't work that's a separate bug, at least), but I don't see a use-case for this separation.
For getting a single constraint I find this even stranger, as a consumer, as there shouldn't be a difference between a constraint that is default or not default.
The places where this is changed in code are not commented, so that is not very helpful either.
Comment #19
fagoRight, this should $definition->getConstraints() give you. Right now, it does not.
The defaults are not optional, they apply always. However, you can use getConstraints() to get the constraints set on the field definition only.
hm, one could argue that $apply_defaults should not exist in the first place, yes. But then we need to watch out to keep the methods idempotent, i.e. you get what you set? I could see improving documentation be enough here, plus then this would work inline with #2236903: setSettings() on field definitions can unintentionally clear default values also.
Comment #20
fagook, so here is a patch following #19, i.e. always add defaults and document that.
Comment #21
yched CreditAttribution: yched commentedCan't really formulate whether it's connected to this needs for "with defaults / without defaults", but I have a vague feeling that we're using the same API for two things here:
- get a list of "constraint definitions" (arrays) - either those that are implicitly enforced by the TypedDataManager, and / or those that are additionally explicitly set by the data definition at hand.
- get a list of actual, runtime Constraint objects, always instantiated from *all* of above, in order to feed to the Validation API.
The fact that the @return phpdoc for some xxConstraint() methods mentions "Constraint objects", and others "arrays ready to create Constraint objects" feels a bit like an API smell.
Again, sorry, a bit vague, maybe it's not related at all, feel free to ignore.
Comment #22
fagoI don't think it's directly related, but I think it's mostly TypedData::getConstraints() which is different. I'd love to do away with it by having the constraint codeflow as suggested for default values (primary source = definition object), then the metadata-factory of the validator can take over creating the constraint objects and we handle constraint definition arrays only?
Comment #23
fagoI've updated the issue summary. Also this should go in before beta.
Comment #24
tim.plunkettI think the points of #21 are valid, but I don't think this issue makes the situation that much worse.
I think switching according to #18/#19 was the right way to go, thanks @fago!
Comment #26
tim.plunkettStraight PSR-4 reroll
Comment #27
catch26: typeddata-2116341-26.patch queued for re-testing.
Comment #28
fagoI noted that there is an $apply_defaults parameter missing - rerolled the patch and removed it.
Comment #29
tim.plunkettGood looking out.
Comment #30
catchCommitted/pushed to 8.x, thanks!
Comment #33
andypostthere's still 2 @todo left in code
Comment #34
yched CreditAttribution: yched commentedThis was marked fixed but BaseFieldDefinition::create() still has a "@todo cleanup in [this issue]" ?
Comment #35
yched CreditAttribution: yched commentedAlso in BaseFiedDefinition (although that one is more "@todo cleanup after [this issue] is fixed"
Comment #36
benjamin.merkley CreditAttribution: benjamin.merkley at Portage CyberTech commentedStill not completed still see @todo in the code
Comment #37
apadernoI opened a follow-up for what reported in the last comment.