Problem/Motivation
This was spawned from #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata, which we realized would benefit enormously from some missing infrastructure, which is the infrastructure this issue is trying to add. Similarly, other data is missing in our serializations/normalizations, with the same infrastructure needs: #2577923: MenuLinkContent entities pointing to nodes are not deployable: LinkItem should have a "target_uuid" computed property, #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field, #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs.
The approach we worked on looked like this:
To accomplish this:
- We are adding 2 computed field properties to
TextItemBase
.- Then we also have to create 2
TextItemNormalizer
s, one for the Serialization module (default normalization, for thejson
andxml
formats) and one for the HAL module (HAL normalization, for thehal_json
format).- Then of course we need the tests for the normalizers.
Then in #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs which has similar problem of exposing extra read-only information to normalization
We accomplish this by:
- Creating two
ImageItemNormalizer
s , one for Serialization and one for HAL.- Then of course we need the tests for the normalizers.
- But in this case we don't add the computed field properties
ImageItem
You can see how this gets complicated very quickly:
- new normalizers, multiplied by all different normalizations in core (and that doesn't include the contrib normalizations yet, such as https://www.drupal.org/project/jsonapi)
- API-exposing modules not relying on normalizers, such as https://www.drupal.org/project/graphql, would have to again duplicate all this logic
- finally, automatically generating documentation that describes the structure of the generated responses also becomes impossible to automate, because they'd have to hardcode the special cases of every normalizer … which we could do in theory for Drupal core's normalizers, but impossible to know all the contrib/custom normalizers!
In other words: lots and lots of custom code, pretty much duplicated, with slight differences, and it all needs to be kept in sync. 😨😭 In reality, it'll mean each format has different omissions, and each format will have inaccurate response documentation.
Proposed resolution
Add
- computed fields to entities (e.g. image style URLs for
Image
entities) - computed properties to fields (e.g.
processed
forTextItem
fields)
to those things that need to expose information that API consumers currently are missing.
That means they are described by Typed Data. Which means they can be automatically normalized (for json
, xml
, hal_json
, api_json
). They can also be automatically be exposed by GraphQL. And they can automatically be documented by OpenAPI!
A complication here is that computed properties today include for example the entity
property on EntityReferenceItem
fields. That loads/contains the full Entity
object in PHP, as a convenience for PHP code. But it'd be nonsensical/undesirable to normalize each entity reference field's entity
property — it'd be a BC break for all formats for starters, too much data, weird, and so on.
After various different directions explored in this issue, we landed on the approach of adding DataDefinitionInterface::isInternal()
and DataDefinition::setInternal()
, after discussing this with the Entity/Field/Typed Data API maintainers at DrupalCon Vienna 2017 (see #95).
isInternal()
would then default to FALSE
(meaning the data is not internal and therefore would be normalized), except when isComputed() === TRUE
, then it'd default to TRUE
(meaning the data is internal and therefore would not be normalized — this would mean the entity
property on EntityReferenceItem
would be omitted from the normalization).
This would then also match the existing behavior of all normalizers: computed properties are not normalized by default (because \Drupal\Core\TypedData\Plugin\DataType\Map::getIterator()
calls \Drupal\Core\TypedData\Plugin\DataType\Map::getProperties($include_computed = FALSE)
), thus avoiding a BC break.
In other words: it's adding a single boolean flag to Typed Data's metadata that allows normalizers (in core and contrib), custom API modules (GraphQL) and documentation (OpenAPI) to not need custom code, instead allowing them to inspect this simple flag.
Remaining tasks
Sign-off: we got +1s for the general approach from fago (Typed Data API maintainer), amateescu (Field API maintainer), hchonov (Entity API maintainer) at DrupalCon Vienna- Get to RTBC.
User interface changes
None.
API changes
- API addition:
DataDefinitionInterface::isInternal()
- API addition:
DataDefinition::setInternal($internal)
(not on the interface, which is consistent with other flags, and follows Typed Data API maintainers' recommendations) - API addition:
ComplexDataPropertiesNormalizerTrait
— a trait to share common logic that normalizers dealing with\Drupal\Core\TypedData\ComplexDataInterface
objects will need to implement to correctly respectisInternal()
Data model changes
None. (Arguably, internal
should be added to the config schema for field.storage.*.*
— but computed
isn't in there either, so this is again just being consistent. This was also recommended by Field + Typed Data API maintainers at an in-person meeting at DrupalCon Vienna.)
Comment | File | Size | Author |
---|---|---|---|
#160 | 2871591-160.patch | 34.03 KB | tedbow |
#160 | interdiff-157-160.txt | 3.93 KB | tedbow |
#157 | 2871591-157.patch | 33.92 KB | tedbow |
#157 | interdiff-151-157.txt | 797 bytes | tedbow |
#151 | 2871591-151.patch | 33.93 KB | tedbow |
Comments
Comment #2
tedbowThis patch tries this idea starting from #2626924-145: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata
It should still working for exposing processed text but doesn't require TextItem specific normalizers.
I have included an interdiff to show what can be removed from #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata but still get the normalization.
Presumably #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs could also be modified to use this method without requiring ImageItem specific normalizers just adding a "images_styles" computed property.
Comment #3
tedbowComment #4
Wim LeersGreat write-up!
I added formatting and clarified some things. Please confirm that my changes are all correct, in other words please confirm that I did not misinterpret.
I personally think this approach makes a ton of sense. Especially after looking at the patch:
Hurray!
So this is the only difference in the original patch versus this one.
And that's only because
TextItemBase
'sprocessed
property does not include cacheability metadata, so to not break BC, a new computed property had to be defined, and it has this slightly different name. Makes sense.Comment #6
dawehnerDo
any reason for this to be public?
I guess its
string[]
?Nice!
Oh, isn't that an API change? We could support that by using a hashmap instead of a list in the annotation.
It feels like in an ideal world we would wrap the serializer to do this for us.
Comment #7
Wim Leers#6.3: note that this is a change relative to the patch in #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs. It's not a change relative to HEAD! HEAD doesn't output
processed
at all. This is what's in HEAD:Comment #8
dawehnerAh good to know! Sorry for the confusion.
In general +1 for naming the field exactly like the computed property. This is IMHO much better.
Comment #9
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedIf the goal is to provide a way to document which computed properties are "special" for the Schemata module (which says it works with Typed Data), shouldn't this new annotation property be at the typed data level?
Instead of over-complicating things with this new class, why don't we send some 'return_as_object' setting to the existing class and do the
FilterProcessResult
dance based on that?Comment #10
tedbowUPDATE: I changed my mind since this first part but wanted to leave in my thinking incase others think this makes more sense
@amateescu,
The behaviour for the schemata module would be a benefit but is not the reason for the proposed change.
The issues mentioned in the summary:
#2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata
#2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs
Are about exposing related information in the normalization process. This idea is to simplify the process but also make sure the related information is not lost to the type data aware system like Schemata but not only schemata.
I think the reason to do this on the field level and not the TypeData level is because the Field level is where the normalization level has been created to not expose all computed properties. And it makes sense not to expose all computed properties on expose all TypeData properties on fields.
Would there be other uses of TypedData properties outside of the field system where the default would be to not expose all computed properties?
I don't think so. But could be wrong.
I am not familiar with TypedData uses outside of the field system. One place I do see it being use is \Drupal\Core\Field\FieldConfigBase::getItemDefinition it doesn't a computed property as far as I can tell.
AFTER THINKING ON IT, CHANGED MY MIND
Ok after trying to see how these might be done on the TypedData level I think it could be good idea and doable
Or at the very least something to explore and to get feedback from those who know TypedData better than me.
This patch:
Remove this.
Again the TextItem changes are still here to demonstrate that this is feasible. They should be removed it we decide this is whole issue is good idea.
For example of how to test a property as exposed see \Drupal\text\Plugin\Field\FieldType\TextItemBase::propertyDefinitions
I am not sure about the backwards compatibility implications of 1 and 2 in the list above.
I think functionality it will be the same as using the annotations. I personally like this way better.
Thanks for the suggestion @amateescu!
Comment #12
tedbowOk errors make sense
\Drupal\serialization\Normalizer\ComputedPropertiesNormalizerTrait::getComputedAttributes() now calls \Drupal\Core\TypedData\ComplexDataInterface::getProperties(TRUE) to get all properties including computed ones to determine which ones are "exposed".
This caused 2 error in this same test.
Text.Drupal\Tests\text\Kernel\Normalizer\TextItemBaseNormalizerTest errors.
This test should have been removed because the covered class \Drupal\text\Normalizer\TextItemBaseNormalizer no longer exists.
There is no need to have normalizer for TextItems any more because this issue handles this in a more generic way.
Comment #13
dawehnerI assume we won't commit this to 8.3.x. Given that I'm wondering whether we really need the new interface ... we can add methods to interface, especially when there is a clear base class (
DataDefinition
).IMHO we should explain what exposing means ... aka. it will be returned in the normalization process / REST
I like some knowledge transported via tests.
Comment #14
Wim Leers#9: That's a very interesting point! Typed Data is the Single Source of Truth, that's how REST/JSON API/GraphQL can work, that's how Schemata/OpenAPI can work. And that's indeed the point of this issue: to not put extra logic in normalizers, but instead provide additional metadata. I think you're right in pointing out that this metadata does not belong in the
@FieldType
plugin annotation. Because in the end, a@FieldType
is just a unit of abstraction, a unit of packaging. A unit/level of Typed Data. So why then put this metadata in that unit, rather than in the general system, which is the Typed Data API?Great insight, thank you! :) In hindsight, it seems so obvious :D
(However,
makes little sense to me, but maybe I'm misunderstanding this.)#10 + #11:
Is "exposed" the right term?
I wonder if we want "normalizable", "serializable" or "exportable". Because this is really about exporting, normalizing, serializing Typed Data-based data structured for use in contexts other than PHP.
REST is one example. But transporting this data via https://en.wikipedia.org/wiki/Protocol_Buffers for example would be another use case.
Isn't this a misnomer at this point? It's no longer about computed properties alone…
These could be simplified to
static::nestedKsort()
Rather than explicitly checking if computed fields/properties implement this interface and then also return TRUE, shouldn't we just be doing this for all fields/properties automatically?
And shouldn't we ensure that all non-computed fields default to
exposed === TRUE
?Then there's no API change. There's just a need for computed fields/properties to explicitly opt in.
So then:
That seems like a simple, elegant, non-BC breaking approach?
(Of course, I may very well be forgetting about some things! In which case, I'd love to hear about that in the comments :))
Comment #15
Wim Leers#13
Comment #16
andypostrelated issue shows that some complex properties does not fits in annotation
Comment #17
tedbowOk here is a patch that addresses some of the feedback
Re: #13
UPDATE: I was wrong here see #21
After looking we don't have such a clear base class.Interfaces that extend this interface\Drupal\Core\TypedData\DataReferenceDefinitionInterface
\Drupal\Core\TypedData\ListDataDefinitionInterface
\Drupal\Core\TypedData\ComplexDataDefinitionInterface
I don't like the idea of introducing the new interface but just want to be clear it is will be more than just updating 1 base class.If this change to DataDefinitionInterface would break 8 base classes in core is it going to be a headache in contrib and custom code?
#13.1 and #14.2
Yes exposed is probably not the right term. but this patch doesn't fix that.
#14.3
This part of the patch probably needs to be removed because it was need explicitly for TextItem processing.
@todo Remove all portions that were only needed for #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata
@todo Write tests that prove "exposed"(or whatever we end up calling them) properties are available in normalized output.
#14.4
Yes updated \Drupal\Core\TypedData\DataDefinition::isExposed() to return TRUE if it is not computed and 'exposed' has not been set.
Ok, if we really want this to be not specific to the normalized process then the normalizer should not be responsible for determining which properties are exposed.
This patch overrides getIterator() in FieldItemBase so that a loop like:
Will always loop through exposed properties.
This simplifies the the normalization process.
Then:
Removed ComputedPropertiesNormalizerTrait
Add FieldItemPropertiesNormalizerTrait which can be used by both FieldItemNormalizers from json and hal_json normalizers.
UPDATE:Ok looking how this would affect JSON API. Since \Drupal\jsonapi\Normalizer\FieldItemNormalizer::normalize is also using
Then they would automatically be getting these new "exposed" fields. From my discussions with e0ipso, I don't think this is inline with that modules assumptions at least the exposed properties in the related issues.
So will update this patch anyways but I don't think overriding getIterator() in FieldItemBase is the right approach.
So probably this would be up to FieldItemPropertiesNormalizerTrait loop over the properties.
Another idea would be to add getExposedProperties() to FieldItemInterface
Comment #18
tedbowThis patch removes overriding getIterator() in FieldItemBase.
Instead it adds \Drupal\serialization\Normalizer\FieldItemPropertiesNormalizerTrait::getExposedProperties.
Again if we did add isExposed() to DataDefinitionInterface this would be much simpler. But as I noted #17 it is not just as simple as updating 1 base class.
Comment #21
tedbowTest failure in #18 makes sense.
In EntityReferenceFieldItemNormalizerTest
\Drupal\serialization\Normalizer\FieldItemPropertiesNormalizerTrait::getExposedProperties would rely on this returning all current none computed properties.
New patch which does:
1) If we want to introduce the concept of "exposed" properties in TypeData it should not be up the normalization system to determine which properties are exposed.
This patch adds \Drupal\Core\TypedData\ComplexDataInterface::getExposedProperties()
Since the doc says
Everything that implements is going to need to now have the concept of "some of the my contain properties can be exposed"
2) Removes ExposableDataDefinitionInterface
I was wrong in #17
So adding isExposed and setExposed() to DataDefinitionInterface
Then creating ExposableDataDefinitionTrait to implement these methods.
Then ExposableDataDefinitionTrait just has to be used in:
Removing ExposableDataDefinitionInterface makes \Drupal\Core\TypedData\ComplexDataWithExposedPropertiesTrait::getExposedProperties() introduced above much simpler
Unit test will probably fail because of expected calls.
Comment #23
dawehnerI am wondering whether this should actually be the behaviour if you iterate over the entries in a complex data?
We could then even drop the method again, but I'm just thinking out loud here.
Comment #24
Wim LeersThis sounds promising! I'll hold off on reviewing until the patch is green, unless you want me to review before then.
Comment #25
tedbowOk this hopefully get the patch to green!(fixes EntityReferenceFieldItemNormalizerTest locally for me)
Also updating the title to reflect that this is no longer FieldType plugin specific.
Re #23
Meaning drop getExposedProperties() and just override getIterator() in base classes that implement ComplexDataInterface?
I think a problem would be that would be that we are using "exposed" to on computed properties that should be available when somehow exporting TypedData.
Presumably we are going to be using this process in the future for computed properties that should be "exposed" but where computing the fields could actually very expensive. So it seems making this the default behavior in getIterator() would be expensive
Comment #26
tedbowThe reason this interdiff is so huge is that this patch removes all change that were only needed for #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata
Those changes were only left in to prove exposed properties worked while I was working on this patch.
This patch also adds \Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestExposedPropertyNormalizerTest
Test the "exposed" properties normalization through the REST process.
There will need to be other tests.
I will update #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata with a patch that only has what is needed for "processed_result" property when using this patch.
Comment #27
Wim Leers#25:
This makes sense I think.
#26: Yay, a clean, focused patch!
For which aspects is test coverage missing then?
Patch review:
This interface is not field-specific, so should not mention it.
Look at the
\Drupal\Core\TypedData\ComplexDataInterface::getProperties()
docs for inspiration.Interestingly,
DataDefinitionInterface
does not containsetRequired()
etc, in fact: this is the only setter, besidesaddConstraints()
.Yet
\Drupal\Core\TypedData\DataDefinition
does have asetRequired()
method. So it doesn't live on the interface.I wonder why?
Does that mean this method shouldn't be added to the interface either?
(Perhaps the intent is that only getters are on the interface, no setters?)
Nit: You can leave this comment out; we don't have it at
\Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase::createEntity()
eitherNot just for computed field properties.
I think this really needs a review from a Typed Data maintainer (of which there's only one: fago), or at least an Entity/Field API maintainer.
Comment #28
Wim LeersNote that as of #2626924-151: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata, this is hard-blocking #2626924.
And soon it will likely also be a hard blocker for #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs.
Comment #29
Wim LeersDiscussed with @tedbow.
The test coverage that's missing, is the cacheability metadata that these computed properties may have.
I think we can do that quite easily here: add a class similar to
class FilterProcessResult extends BubbleableMetadata
, which then carries this computed string plus its cacheability metadata (let's say it varies by theuser
cache context).We can then test for that in the test, by overriding
\Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::getExpectedCacheContexts()
.Comment #30
tedbow@Wim Leers thanks for review!
#27
1. fixed
2. Yes I wondered this too. Didn't know if this was an oversight or intentional.
3. removed
4. Good point. So this also means that the whole trait should be changed from FieldItemPropertiesNormalizerTrait to ComplexDataPropertiesNormalizerTrait.
Further for the serialization module instead of invoking this in FieldItemNormalizer::normalize() this should be called in \Drupal\serialization\Normalizer\ComplexDataNormalizer::normalize() and FieldItemNormalizer no longer needs to override normalize().
For the HAL module there is no ComplexDataNormalizer so no change needed there.
This change doesn't break \Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestExposedPropertyNormalizerTest but it does break \Drupal\Tests\serialization\Unit\Normalizer\ComplexDataNormalizerTest but I think because the test is written.
The including \Drupal\Tests\serialization\Unit\Normalizer\TestComplexData doesn't implement the interface correctly. i.e. \Drupal\Tests\serialization\Unit\Normalizer\TestComplexData::getProperties returns no value so that breaks the trait.
Haven't fixed this test yet but lets see if it breaks anything else.
#28
Ok now testing this.
ExposedStringData is now returning the new \Drupal\entity_test\ComputedString which extends BubbleableMetadata.
Providing cache context and tags.
Comment #32
tedbow#30
Well this is an interesting failure
Not what I expected.
So EntityNormalizerTest is causing a failure because it is calling the new ComplexDataPropertiesNormalizerTrait::normalizeProperties() but it is sending an object that doesn't implement ComplexDataInterface.
This is because what I did in point 4 in #30(from review in #27).
So I was thinking that since the logic in ComplexDataPropertiesNormalizerTrait::normalizeProperties() worked with anything that implemented ComplexDataInterface I could just call this in ComplexDataNormalizer::normalize() because in ComplexDataNormalizer there is
protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ComplexDataInterface';
So I thought that in ComplexDataNormalizer::normalize()
$object would always implement ComplexDataInterface
Because \Drupal\serialization\Normalizer\FieldItemNormalizer extends \Drupal\serialization\Normalizer\ComplexDataNormalizer
FieldItemNormalizer has
protected $supportedInterfaceOrClass = FieldItemInterface::class;
And FieldItemInterface extends ComplexDataInterface
So I made the assumption that any normalizer that extended ComplexDataNormalizer would have a $supportedInterfaceOrClass that extended( or implemented) ComplexDataInterface
This is actually not the case and this causes the failure.
EntityNormalizer extends ComplexDataNormalizer
\EntityNormalizer::$supportedInterfaceOrClass = [EntityInterface::class];
EntityInterface does NOT implement ComplexDataInterface
So in EntityNormalizerTest::testNormalize() line 81 fails
$this->entityNormalizer->normalize($content_entity, 'test_format');
$content_entity does not implement ComplexDataInterface
EntityNormalizer doesn't override ComplexDataNormalizer::normalize()
So this causes a failure.
Whew! Is this confusing to anybody else?
Question 1: Given that why aren't all the Entity REST tests failing?
This is because none of the Entity REST tests are actually using EntityNormalizer directly. All the REST entity tests deal with entities that implement either ContentEntityInterface or ConfigEntityInterface.
So the entities are normalized with either ContentEntityNormalizer or ConfigEntityNormalizer
Both of these normalizers have overridden normalize() so ComplexDataNormalizer::normalize() is never called.
Question 2: Why was EntityNormalizer working before this patch?
This is because ComplexDataNormalizer::normalize() did this:
So $object just had to implement Traversable
Moral of the story
Basically because the way we are extending normalizer classes for any normalizer in the Serializer module we cannot guarantee that $object sent its implementation of \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() will implement the interface specified in its $supportedInterfaceOrClass
Even though doc for $supportedInterfaceOrClass says
Weird.
Comment #33
tedbowSo how should we fix the problem described in #32?
Option 1: Roll back changes to ComplexDataNormalizer::normalize()
This doesn't seem right because since amateescu pointed out #9 these exposed properties are more of TypedData thing. It has seemed to make more sense.
But moving the logic back to FieldItemNormalizer would work.
Option 2: Leave changes to ComplexDataNormalizer::normalize() and override normalize in EntityNormalizer
This would also make the tests pass because EntityNormalizer would be like ContentEntityNormalizer and not call ComplexDataNormalizer::normalize() with an $object that doesn't implement ComplexDataInterface
But if there were any contrib or custom normalizers that were like EntityNormalizer in that they extended ComplexDataNormalizer, did not override normalize() and their $supportedInterfaceOrClass did not implement ComplexDataInterface they would immediately break.
This seems like a BC break.
Option 3: In ComplexDataNormalizer::normalize() check if $object implements ComplexDataInterface
I am not huge fan of this but it seems like the only option that
In addition we could use
We could use \Drupal\Core\Entity\Plugin\DataType\EntityAdapter::createFromEntity to create a TypedData object from an Entity when sent an Entity.
But since
I am going to try option 3 in this patch.
Other ideas?
UPDATE:
chatted with @larowlan on IRC and he helped with extra info about EntityAdapter::createFromEntity and pointed out the Entities use to implement ComplexDataInterface early in the Drupal 8 development cycle(didn't know that). So that is probably why the normalizers are setup this way. Super helpful!
Comment #34
larowlanCause you're only calling ->getExposedProperties here, I think your solution may be to add a new interface, then move getExposedProperties from ComplexDataInterface to the new interface. Then have both ComplexDataInterface and EntityInterface extend it. Then the implementation in EntityInterface can do something different - but you're typehinting for the method
Comment #35
tedbow@larowlan that solves the problem only for EntityInterface but doesn't solve
So any contrib or custom normalizer that followed the pattern that core provided, i.e. extending ComplexDataNormalizer for a $supportedInterfaceOrClass that does not extend will have their normalizer immediately break.
I am sure this something we offer BC for but that feels like bad DX.
Also adding getExposedProperties to when we don't actually need the concept of exposed/not exposed properties on Entities right now therefore adding complication that it is not necessary.
If BC break for custom/contrib normalizers is not concern then we should just override normalize in EntityNormalizer. I would be as simple as
But then I look at ComplexDataNormalizer and it is only overriding normalize() and setting $supportedInterfaceOrClass from NormalizerBase.
So why not have EntityNormalizer extend NormalizerBase directly. Entities are not complex data in the TypedData sense(though I guess they were at one point)
Then we would just have to override normalize() in EntityNormalizer and do what ComplexDataNormalizer::normalize() did before this patch.
Then only class that will extending ComplexDataNormalizer directly FieldItemNormalizer which actually sets $supportedInterfaceOrClass to an interface that extends ComplexDataInterface.
Updating the patch for this.
UPDATE:
Just noticed the doc for ComplexDataNormalizer says
So that is *very* wrong, but guess probably comes from the history of Entities once being and implementation of TypeData.
Comment #36
mradcliffeExplaining my motivations as a contrib developer:
I have one contrib normalizer that extends ComplexDataNormalizer so that I can use its normalize method. My $supportedInterfaceOrClass doesn't extend ComplexDataInterface itself, but all the classes that implement it extend Map at some point so I think I would be okay?
I have another normalizer that extends ComplexDataNormalizer but targets my custom sub class of ItemList. I should probably just change that to ListNormalizer. I think I felt it was easier to be able to iterate over the list items and call parent::normalize. I liked that better than calling the serializer property's normalize method. Looking at things again I don't think there's a big performance hit there like I thought.
So in my case I think I would be okay with a BC break here, but I don't know about other contrib. that would be in the same spot AND be providing custom stuff. The burden would probably be mostly on Drupal site builders who may be using a custom module developed by someone else.
Comment #38
tedbow@mradcliffe thanks offering a real contrib example/experience.
Depends with which way we go.
(emphasis added)
I think also it would be on anyone that upgraded to 8.4.0(if this gets in) who is using a contrib module that does what you are describing where the module maintainer has done what you have done(which core has not suggested that you not do) and has not updated their normalizers.
I see you help maintain the TypedData documentation so I guessing you are more likely to find this issue and notice a change record than the average contrib maintainer ;)
So my guess is patch in #33 is the safest with maybe the logic handling non ComplexDataInterface objects being marked as deprecated if decide it should be.
Other follow updates would be:
Comment #39
dawehnerWell, for me everything which is doing that, basically introduces a big in their code. I think this is one of those signs where we mixed up what single responsibility principle means. Its not about code sharing, its about having logical abstractions. Of course there is nothing to change here.
Note: This just works because
\Drupal\Core\Entity\ContentEntityInterface
extends \Traversable. This is not necessarily true for generic entities. To be honest there is not much we can actually do about it.I think we don't actually need this use statement, right?
Comment #40
tedbowOk this is #33 rerolled plus #39.2
#35 will not work because of reasons mentioned in #38 and #33 appears to be the safest.
We can't assume that
$object
sent to \Drupal\serialization\Normalizer\ComplexDataNormalizer::normalize is implements ComplexDataInterface.Comment #42
Wim LeersLet's get this moving forward again?
Comment #43
Wim LeersSo let's get this going again.
This fixes one of the two failures. The other failure requires
\Drupal\Tests\serialization\Unit\Normalizer\TestComplexData::getProperties()
to be fixed. @tedbow is best placed to fix that.Comment #44
Wim LeersFFS d.o.
Comment #45
tstoecklerWhile I dislike adding something that is onely used for Rest to the generic
ComplexDataInterface
, I don't really see a way around it. However, it should be thoroughly documented what "exposed" in this case means and what it doesn't mean.On a more general note, I'm not sure this is actually needed. The issue summary lists #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata and #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs as examples of where we want to include computed properties as part of normalized output. However, is there ever a case where we do not want that? At least by default I mean. Field types can always specify dedicated normalizers, but this issue exists to try to avoid having to do that just because you have a computed property. So I am wondering whether it wouldn't make sense to just include computed properties by default and be done with it (I guess controlled by a
bc_*
config value, but...). Entity reference fields have a computedentity
property that could be problematic but entity references need special handling anyway (proven by the fact that both Serialization and HAL modules provide dedicated normalizers), so I don't think that's a particularly strong argument against this. (And in some cases it can even be pretty neat to have the serialized referenced entity as part of the normalized output.) Thoughts?Comment #47
Wim Leers@tstoeckler note that #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata is adding a new
process_result
computed property, because the existingprocessed
computed property cannot be used due to it not containing the necessary cacheability metadata. Cacheability metadata is necessary for REST/JSON API/GraphQL responses to be cacheable.So I'm afraid that even just that means that your proposal doesn't work. (And auto-detecting whether the value contained in a property implements
CacheableDependencyInterface
or not also doesn't work, because the defaultNULL
value for empty non-required fields would then still get in the way.Furthermore, e.g.
LanguageItem
'slanguage
property is computed but we wouldn't want that one — it only makes sense for internal (PHP) use. Exactly the same is true forDateTimeItem
'sdate
computed property andDateRangeItem
'sstart_date
andend_date
computed properties.In other words: there's quite a few examples of "convenience" computed properties that can only ever make sense in a PHP context. We need to somehow figure out which those are.
You could read
getExposedProperties()
asgetNormalizableProperties()
orgetPropertiesThatMakeSenseOutsideOfPhp()
.Comment #48
tedbow@tstoeckler re #45
I think the problem would be if this was default any field item in contrib or custom code would automatically pick up this behavior. @Wim Leers pointed out a couple problems in core but we have no idea how this would effect contrib and custom field types because we don't know what computed properties they would be using.
Comment #49
tedbowOk at least some the remaining errors are from changes in #2846554: Make the PathItem field type actually computed and auto-load stored aliases
Basically \Drupal\path\Plugin\Field\FieldType\PathItem::ensureLoaded() was added to make sure that all the properties were loaded when try to get any of the properties. But get() was not overridden so ensureLoaded() was not called.
This patch overrides get() and calls ensureLoaded().
Comment #50
tedbowNot sure what happened there.
Patch attached
Comment #52
tstoecklerNot quite convinced by #47/#48. I just did the following change on a clean 8.4.x:
And did a Rest GET request before and after the patch. This is the resulting diff of the normalized output:
Surely the added information is not necessarily semantically sensible, but our Rest output is super verbose regardless. So basically we are adding knowledge about Rest to the core typed data system, to avoid this bit of ugliness. It is legitimately pretty ugly, so I'm not actually 100% sure that that's a bad trade-off, but I do want to make sure that everyone is aware of the trade-off we are making.
(And yes, #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata will make that even a tad uglier, because of an additional, seemingly duplicated property, but the fact that text fields lose cacheability metadata is a bug anyway (regardless of Rest, it's just greatly exacerbated by Rest) and is neither Typed Data's nor Rest's fault, so I don't think it's a strong argument in either direction.)
Comment #53
tedbowThis patch fixes ComplexDataNormalizerTest for new logic.
It adds a new test function for the case where ComplexDataNormalizer is extended and the extending class has $supportedInterfaceOrClass that does not extend ComplexDataInterface as was described in real contrib situation in #36 by @mradcliffe(thanks again).
Re @tstoeckler comments #45 and #52
We can't just consider how automatically including computed properties would affect core field types. I think for BC we have to consider that contrib or custom code will have field types that have computed properties... Regardless of REST this will change normalized output for any entities that use these fields. Also what happens if there is no normalizers that support the computed properties? Right now that is not problem and contrib modules defining field types with computed properties don't have to worry about these properties going through the normalization process.
If we start to just run all computed properties through the normalization process I fear this could have unforeseen ramifications and expectations.
As stated in the issue summary
Comment #54
tstoecklerWell for BC we could add (yet another) config setting to control this behavior, so I don't think that's a strong argument. I think we should consider how this affects fresh installs and for those I don't see how this breaks anything - it's just ugly. You're right, it would definitely need a bit more testing with contrib, but e.g. entity reference's entity property that the issue summary cites doesn't cause any problems.
Again, I don't want to shoot down the current patch I just don't feel that alternatives have been discussed sufficiently (and that hasn't changed with the latest comments).
Comment #55
tedbowok another reason to put it in typed data.
from @Wim Leers' comment on #14
Putting the BC concerns aside:
If we export every computed property by default but then know that certain normalizers such as entity reference will override this behavior it will be super hard(impossible?) for any system like Schemata to know what the data coming out of Drupal will ultimately look like.
Whether the Schemata module lives on the problem remains how do we automatically information of what will be export from Drupal for REST, JSON API or other modules.
If we move this to normalization process and just allow any particular normalizers to choose this what computed properties to include it would very opaque to any systems outside of Drupal.
Below is a brainstorm of how this might be possible:
If we move this to the normalization process then to get the same effect, to know what exported data properties would available for any entity, we would have to:
this is just 1 idea but I think the key problem it points to is if you just have class like \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem right now in the current normalization process it is impossible to determine which properties will be exposed
I feel like it would get very complicated very quickly if we don't use the TypeData system as the Single Source of Truth.
Comment #56
Wim Leers#49: hah, funny that you found a bug in #2846554: Make the PathItem field type actually computed and auto-load stored aliases, I did too, at #2824851-73: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior. In that case, it was for
::equals()
.#52
is definitely an understatement… thoseentity
keys can never ever ever be remotely useful. We've been going through a lot of effort to make our normalizations better (e.g. #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX). Making the output look like you describe would basically mean we say "screw any user talking to Drupal viajson
orhal_json
:( Our normalizations may be verbose (and yes, I totally agree with that!) and not ideal (totally agree with that too!), but at least every single thing you see contains correct, actionable data. That would no longer be true. In fact, it'll literally look broken.We're not arguing to add "REST knowledge", we're arguing to add metadata that allows code to know which properties make sense to serialize at all. This would also help e.g. https://www.drupal.org/project/default_content. It would help core REST. It would help https://www.drupal.org/project/jsonapi. It would help https://www.drupal.org/project/graphql. And it will help anything else of the like in the future. In a nutshell: it allows us to know which properties make sense to export or normalize or serialize or freeze or represent or render — whatever term you prefer, it's the same concept: the ability to take data stored in Drupal and represent it in a non-PHP-object way, so that it can be shared, stored, reused …
RE:
— yes it's a bug, but it's a bug that we cannot fix. Fixing it implies changing the type (i.e. fromstring
to\Drupal\filter\FilterProcessResult
), which is a BC break and therefore not allowed.#54:
This is inaccurate.
Today, many properties are missing in our normalizations. For
TextItem
, it's theprocessed_result
property. ForLinkItem
, it's theoptions
property. ForImageItem
, it's image style URLs. And so on. This affects core REST (in bothjson
andhal_json
formats), as well as https://www.drupal.org/project/jsonapi, as well as https://www.drupal.org/project/graphql.If we use
getProperties(TRUE)
, then we get things in the output that are definitely unacceptable. And that's just core. For field types in contrib modules, we don't know what's stored in properties — there could be sensitive things in there. And there definitely will be those similar examples of things that only make sense in PHP land to normalize (like theentity
property onEntityReferenceItem
).You say that BC layer would allow you to choose to keep the current behavior (which would mean no processed text, a.o.) or opt in to the new behavior (which results in clearly unusable normalizations). But … that's choosing to either not get all the data, or to get broken data. How is that a sensible choice?
is hugely euphemistic, and is ignoring the real DX consequences. It would instantly disqualify Drupal.
I completely understand your POV: you're an Entity/Field/Typed Data API maintainer. You want to keep things as simple, consistent as possible. You want to ensure that responsibilities are cleanly delineated. But … the whole reason Typed Data exists is so that there is sufficient metadata to describe the data structure, purpose, capabilities, restrictions etc of every entity field and every entity field property. I'd love to be able to use
setComputed()
instead of dealing with this issue, but there's #2392845: Add a trait to standardize handling of computed item lists. IfsetComputed()
would actually mean "is calculated value" and we'd have aisConvienceProperty()
for things likeentity
anddate
, then we'd be able to use that. Perhaps that's an approach that you'd like better?That would solve 90% of our problem here!
Comment #57
andypostBtw for default content it ould allow to filter user's internal fields or manage that more preciesly
Comment #58
dawehnerI totally agree with @tstoeckler that adding more and more stuff to the typed data stuff smells wrong. Its already an almost non understandable system, we should aim for simplicity.
I'm always fascinated by new methods which themselves just call out to public methods. For example in this case it would not be a trait, it could be a helper living somewhere and we just call it with a complex data object. It feels like we are ignoring one dimension of programming in general.
I think all what tstoeckler is saying: Maybe we should put this exposed vs. not exposed level into an internal setting and get it via
getSetting
instead. Would that make sense?Comment #59
tstoecklerThanks a lot @tedbow and @Wim Leers for those write-ups. They put this issue in a much better perspective for me. I will write a proper technical reply next week but need to re-read and process a bunch of things first. Just wanted to make sure you know I greatly appreciate comments like #55 and #56. I know it takes not only a lot of time but also a lot of emotional effort to argue something that you are convinced of with someone coming from a completely different angle. Thanks!
Just a quick note re #58.2: Actually what @Wim Leers thought I was saying, was in fact what I meant. As far as I know off the top of my head, typed data itself doesn't have the concept of settings, that's only on the field level. (I always get this stuff confused, though, so I may be wrong.)
Comment #60
dawehnerThis is totally true for generic typed data, but its not true for
\Drupal\Core\TypedData\ComplexDataInterface
. ... which is what you can potentially iterate over. All I try is to avoid adding "yet another" thing, even well, if we want to be API first, having these ideas as a first class citizen maybe make sense.Comment #61
Wim Leers❤️
Comment #62
damiankloip CreditAttribution: damiankloip at Acquia commentedI think this general approach looks good, I read the previous discussion for the reasoning (putting this at the typed data level) and agree this seems like the only real place this can live to work properly. Here are some other comments:
I agree with Daniel here too I think - it's kind of weird when traits have a hidden dependency on the class that is using the trait.
It could be clearer to just return the result of array_filter here?
It would be nice if $exposed had a default value of TRUE here. It's small but being able to do setExposed() in the chain is a bit nicer.
This comment and the code don't match. it doesn't default it still checks the configured definition property. I think this could be condensed down to
return $this->isComputed() && !empty($this->definition['exposed'])
?Same as interface comment, maybe defaults to TRUE..
Is this needed for this patch? Just wondering.
We could have another normalizer that is just for traversable objects? we could then have our normalizers extend that? We could keep the ComplexDataNormalizer in place for anything using it already in contrib etc.
Side note - yes when this was implemented entities did implement the ComplexDataInterface..
Usually normalization produces a string or array of data from objects, not objects. Looks like this is to do with the test coverage more than anything? Generally if it's an object it should have a normalizer, and normalize() would return the data that's used. This seems like kind of a hack of the normalization layer with some in-place normalization. We have a normalizer for Markup objects I think, I guess this is not one of those.
Comment #63
tedbow@damiankloip thanks for the review
6. yes this needed see #49 7. This sounds like follow up because we need to cover both cases anyways in ComplexDataNormalizer because as noted elsewhere the class is already being extended in Contrib for classes that don't implement ComplexDataInterface
Comment #64
damiankloip CreditAttribution: damiankloip at Acquia commentedAh sorry, I missed that comment... Does that mean we need a small addition to some test coverage for that too while we're here (sorry)? I.e. something quick to cover the case in \Drupal\Tests\path\Kernel\PathItemTest ?
Comment #66
tedbowdeleting comment, moving to #70 with correct patch
Comment #67
tedbowComment #69
tedbowWhoops I did the patch against 8.4.
Also need to fix \Drupal\Tests\serialization\Unit\Normalizer\ComplexDataNormalizerTest
Comment #70
tedbowSorry @all that this has taken me so long to get back to.
#62
1. Remove the trait and create \Drupal\Core\TypedData\TypedDataHelper
2. fixed
3. Set default to TRUE
4. Actually I think the comment does match. If we changed it to
return $this->isComputed() && !empty($this->definition['exposed'])
Then only computed properties could ever be exposed. But all non-computed properties should be exposed unless
$this->definition['exposed']
is expliciting set to TRUE.5. fixed
6. re #64 add test coverage to PathItemTest. The trick is that for isEmpty and the 2 ways to get the property values you have to make sure that none of the other ways is called first or ->ensuredLoaded() would have already been called.
7. see #63
8. This was added to make sure it will work with logic needed for #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata. We could remove this here and add it back there if needed(I think it will be).
Also updated ComplexDataNormalizerTest, EntityReferenceFieldItemNormalizerTest, TimestampItemNormalizerTest removed expected calls to
getExposedProperites()
. I created \Drupal\Tests\serialization\Unit\Normalizer\TypedDataTestTrait because all of these these tests had the same code.Comment #71
Wim LeersSorry for the lack of reviews here. My bad. Let's get this going again!
Q: Can we remove all the
PathItem
-related changes after #2905524: The count() method used on PathFieldItemList behaves differently if isEmpty() has been called. lands?I think that makes sense.
Q: Can you create a change record (which will be necessary anyway), which would then also make it easier to understand and review this patch!
Unnecessary change here.
Nit: Let's add an
@see
?I think the logic is correct, but perhaps a bit confusing. The following would be clearer I think:
Nit: this seems unnecessary?
This feels a bit strange. I'm not sure yet how to do it better though.
Can't these changes happen in their own issue? Ideally this issue would only build infrastructure.
I'd expect also a
field_test_unexposed
to be added here, to prove that we're not just exposing everything.I first had a hard time understanding this. But it makes sense:
class EntityNormalizer extends ComplexDataNormalizer
, without overriding thisnormalize()
method, therefore this may also be called withEntityInterface $object
.IDK how to make this comment clearer. It's good enough for now.
This is weird. Why do we need this?
Nit: s/retreiving/retrieving/
The name is super generic. I think
ExposedTypedDataTestTrait
probably better?The docs for the interface say this:
Therefore I think this needs to become
Which means you can probably also remove the
(string)
casting inComplexDataPropertiesNormalizerTrait
— i.e. my point 9 above and @damiankloip's remark in #62.8.Comment #72
tedbow1. fixed
2. added
3. fixed
4. removed
5. Previously in this patch this used be on ComplexDataInterface and used a trait for the implementation. #70.1 from suggestion of @dawehner in #58.1 and agreement from @damiankloip in #62.1 . Yeah couldn't think of a better way.
6. Removed changes. @todo Need to find where this is being fixed in other issue. this will cause the test to fail though so will have to postpone this on the other issue.
7. Actually we need to test this on the property level not the field level. This pointed to some confusing things in the test.
ExposedStringData
toComputedStringData
because it is not exposed on this level just computed, changed id tocomputed_string
\Drupal\entity_test\ComputedString::__toString
to return"Computed! " . $this->notComputedValue;
. There is no concept of exposed here.ExposedPropertyTestFieldItem
to have 2 properties of thecomputed_string
,exposed_value
andnon_exposed_value
. The only difference in the 2 issetExposed()
call. This should allow testing that only difference in the properties that 1 is exposed and gets output in normalization.EntityTestResourceTestBase::getExpectedNormalizedEntity
(besides comments) is s/Exposed!/Computed!/.non_exposed_value
doesn't get returned.8. ok
9. See @damiankloip's #62.8 and my response #70.8 will be need for stuff like #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata but not tested here. should we removed for now.
10. fixed
11. yes, renamed
12. Ok adding cast and removing logic mention in 9)
UPDATE: Nope can't do 9) because it breaks the cache that are expected when using \Drupal\entity_test\ComputedString because it extends BubbleableMetadata
Need to figure that out but don't have time now. Not address in this patch but will try.
Comment #74
tedbowRemoving this causes test to fail.
I created an issue just for this #2908674: When using get() method directly on PathItem the alias is not loaded. We will need to get that issue committed first but can still work on other problems here.
Looking at #71.12 and #62.8
@damiankloip's comment
In this patch I simplified
ComplexDataPropertiesNormalizerTrait
but had to change a couple things in a couple normalizers to get cache tags to work. I wanted to see if we can handle cache tags in a generic way for computed fields for a case like #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata. If we can't and you have to make a bunch of specific normalizers I think it would take away a lot of the benefit of this approach.Also including a patch with proposed changes in #2908674: When using get() method directly on PathItem the alias is not loaded to see if the test failures are from that issue.
Comment #77
Wim LeersI'll hold off reviewing this until it's green again, because it seems that some of the things I pointed at, and were changed, play a role in this patch now failing. I might be reviewing something that's going to change anyway.
Comment #78
tedbowI chatted with @Wim Leers and asked if he thought it would make sense to remove the cache tags/context logic from the current test case. He agreed so this patch removes all that hopefully it will be easier to review and will stick more to is described in the title and IS.
Eventually we will need to handle caching of these computed properties for cases such as #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata but not in this issue.
I think the previous patch proves that will be able to handle but that should be a follow up issue.
Tests will still fail because of #2908674: When using get() method directly on PathItem the alias is not loaded but this patch fixes \Drupal\Tests\serialization\Unit\Normalizer\ComplexDataNormalizerTest::testNormalizeComplexData
Comment #80
tedbowWhoops ☹️. fixing
And coding standards fixes.
Comment #82
tedbowRe #77 @Wim Leers' point about reviewing, lets confirm the failures are only because of #2908674: When using get() method directly on PathItem the alias is not loaded by testing the patch in #80 plus the patch in #2908674
Comment #84
tedbow☹️😬😱 Random failure because of #2902191: Determine cause and fix random fail in \Drupal\Tests\settings_tray\FunctionalJavascript\SettingsTrayBlockFormTest::testBlocks. I hadn't notice that issue wasn't RTBC anymore because of rerolls, set back to RTBC.
retesting #82 but there were no other fails on that issue so I think it is ready for review.
Comment #85
Wim Leers#78: +1. I think it's important that we keep ensuring that cacheability bubbling can be layered on top without BC breaks though. So can you also open an issue for that?
So these hunks will go away once #2908674: When using get() method directly on PathItem the alias is not loaded lands. Great.
All changes here are related to cacheability too. They should be removed from this patch — they were apparently overlooked in #78.
The comment describes something that does not exist?
This comment isn't very useful. We can remove it.
Can be simplified to:
More importantly: let's choose a value that is not the same as the value set in
createEntity()
.We don't need this anymore, see #78 again.
If we'd have that follow-up that #78 describes (and which I asked for earlier in this comment), which would layer on cacheability bubbling, then the structure of this code will make far more sense too! :)
Comment #86
tedbow@Wim Leers thanks for the review!
Created follow #2910211: Allow computed exposed properties in ComplexData to support cacheability.
Note, the interdiff is from #80 because #82 was just to prove test failures are because of #2908674: When using get() method directly on PathItem the alias is not loaded so this should 6 fails like #80
Comment #88
Wim LeersPathItemTest
in this point! I was indeed pointing toResourceResponseSubscriber
.#86 failed because:
This now has weird indentation… which was also hiding a functional change, which also caused failures.
Comment #90
Wim LeersTrying to do some really comprehensive reviews to get this one to an RTBC'able point.
Doesn't this also mean that the config schema should be expanded? i.e.
field.schema.yml
s/is/should be/
Changing this comment to be similar to what
\Drupal\Core\Plugin\Definition\DependentPluginDefinitionTrait
and\Drupal\system\Tests\Entity\EntityDefinitionTestTrait
.This does not document what the keys are. Done.
This is only used in one place. Which means it doesn't make sense for this to be in a separate "helper" class. We can still extract it later.
(In #70, it used to be necessary, because many other things called this, but that's no longer the case. So we can now simplify this, yay!)
Done.
This is testing the
json
format. We should also testhal_json
.Nit: This can be simplified a bit more still.
s/normalizer/normalizers/
Fixed.
This is accurate, but an
@see
would make it clear for which classes this can be used. Done.Why do we need this change?
Why doesn't
\Drupal\serialization\Normalizer\PrimitiveDataNormalizer
already cover this?Nit: we don't do
camelCase
in variables, only on class properties.Fixed.
Nit: missing "exposed".
Nit: missing "properties". Should be named
ExposedTypedDataPropertiesTestTrait
"exposed property" vs "exposed string test" vs "exposed property (test)" vs "exposed property test" vs "exposed string".
Let's make this consistent.
I propose:
@FieldType
=exposed_property_test
@DataType
=computed_string_test
Rather than specifying these explicitly, we can extend
field.storage_settings.string
. (field.storage_settings.uri
also does this, for example.)Done.
I'd expect this class to be living in the same directory as the
@DataType
plugin that uses it.Should be marked
@internal
explicitly, because it is coupled to a@DataType
plugin — it's an implementation detail of that.I think
$sourceString
or$sourceValue
would be a better name.This is not exposed, but computed. Fixed.
This comment does not belong here anymore. Removed.
one string, and two computed string values
Nit: These are computed. The labels imply otherwise. Fixed.
I only did string fixes and removed the
TypedDataHelper
class, and merged its logic in the sole caller. In other words: only trivial things, so I can continue to review this.Comment #91
Wim LeersOf the 22 remarks in #90, I only addressed #2, #3, #4, #5, #7, #8, #9, #11, #12, #13, #15, #19, #20, #21, #22.
Still to be done: #1, #6, #10, #14, #16, #17, #18. Leaving those to @tedbow.
Comment #92
Wim LeersUpdating issue title to match current patch.
Comment #93
Wim LeersI said this in #90.#6:
You may wonder "why?". In general, you'd be right, this should not be necessary. But:
core/modules/hal/src/Normalizer/FieldItemNormalizer.php
andcore/modules/serialization/src/Normalizer/ComplexDataNormalizer.php
to useComplexDataPropertiesNormalizerTrait
core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php
ensures that both\Drupal\serialization\Normalizer\EntityNormalizer
and\Drupal\serialization\Normalizer\FieldItemNormalizer
inherit this, because both extend\Drupal\serialization\Normalizer\ComplexDataNormalizer
\Drupal\serialization\Normalizer\ComplexDataNormalizer
, and therefore it uses the trait itself tooTherefore, since the code paths are different, we need to explicitly test both formats.
Finally, having written this, I just realized something: shouldn't we expand the test coverage, to not only test exposed vs non-exposed properties on fields, but also exposed vs non-exposed fields on entities?
Comment #94
tedbowI chatted with @Wim Leers and he realized this is not the case but for others
We don't need to worry about exposed/non-exposed field on entities because the changes in
\Drupal\Core\TypedData\DataDefinitionInterface::setExposed
allow us to expose/not expose properties on fields to normalization but don't allow us to do the same thing for fields on entities.Ok actually maybe I wrong but looking what I just said about fields on entities and then looking at @Wim Leers comment #90.1 I think there might be bigger problems with this approach.
Yes it would mean that it all would mean that this this would valid code
But course that doesn't actually do anything because field config doesn't have a concept of exposed or not.
so now we would have add the exposed methods to FieldItemDataDefinition, EntityDataDefinition, among a whole bunch of the others but we are only calling
isExposed()
in\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::getExposedProperties
so everywhere else exposed doesn't actually do anthing😦😖
I am missing something here? So probably adding to DataDefinitionInterface is not the right answer.
Looking back I added this #10 because it seems like a better idea to expose properties on the Typed Data level.
Before that I had suggested in #2 that we add a
$export_computed_properties
to FieldType that FieldType plugins could add to their annotations. Is that a better way?Comment #95
tedbowTalked with @fago, @amateescu, @dawehner, @Wim Leers and @xjm at Drupalcon Vienna
Fago suggested not using "exposed" but instead use the term "internal".
He also suggested so only add the new getter
isExposed()
ontoDataDefinitionInterface
not the setter as this intention(see @Wim Leers question #27.2). So not everything class that implementsDataDefinitionInterface
will be able to be set to internal. This is will be similar to "computed"We have
\Drupal\Core\TypedData\DataDefinitionInterface::isComputed
butsetComputed
is not defined on this interface. It is defined in\Drupal\Core\TypedData\DataDefinition::setComputed
.So this takes care of the problem #94. In the example I mentioned we will not be able to call
setInternal()
onFieldConfig
objects. Just callisInternal()
which will always be false because you can't callsetInternal
orsetComputed
on FieldConfig.If later we want to introduce the concept of internal fields we can add
setInternal
at this level.So not uploading a patch now but here the steps that we will need to take.
\Drupal\Core\TypedData\DataDefinitionInterface::setExposed
setExposed()
to\Drupal\Core\TypedData\DataDefinition
setExposed
fromExposableDataDefinitionTrait
ExposableDataDefinitionTrait
from\Drupal\Core\Field\FieldConfigBase
Might be other things but that is all I can think of now.
Comment #96
Wim LeersComment #97
tedbowHere is the patch the changes described in #95
Comment #98
Wim Leers❓ Should not default to
TRUE
because\Drupal\Core\TypedData\DataDefinition::setReadOnly()
and\Drupal\Core\TypedData\DataDefinition::setRequired()
also don't do this.👍 This removes the setter from the interface, keeping it only on the ipmlementation.
👍 This is the only remaining occurrence of the string "expose" in the entire patch!
s/will not return in normalization/is not exposed in the normalization/
👍 This adds the
isInternal()
method implementation from the trait. Because there is no setter available onFieldConfigBase
and subclasses (it only lives onDataDefinition
and subclasses, whichFieldConfigBase
does not extend), this means that it will fall back to callingisComputed()
, which is hardcoded to always returnFALSE
in\Drupal\field\Entity\FieldConfig::isComputed()
.⁉️ However, the other subclass of
FieldConfigBase
,BaseFieldOverride
, implements::isComputed()
like this:Which means that if a base field marks itself as internal (via
setInternal(TRUE)
, because\Drupal\Core\Field\BaseFieldDefinition
extendsDataDefinition
), then that will continue to be respected.The only example of an entity type that has a computed base field in core is
\Drupal\entity_test\Entity\EntityTestComputedField
.@fago was very explicit in wanting it on
DataDefinitionInterface
, meaning that he knew it'd affect base fields. Which means we must also respectisInternal()
for fields, not just for properties. Which means we'll also need to update\Drupal\serialization\Normalizer\ContentEntityNormalizer::normalize()
to useComplexDataPropertiesNormalizerTrait::normalizeProperties()
, to ensure computed fields are also omitted from the normalization, unless they're explicitly marked as non-internal.This also means we should add REST test coverage for
\Drupal\entity_test\Entity\EntityTestComputedField
, because right now its computed field (and hence internal by default) is being exposed, it should not be.Also pinged @fago for feedback here, that's why I'm leaving this at .
Comment #99
tedbowre @Wim Leers comments on #91 regarding his review in #90
1. Now with the in #97 this will no longer be necessary because you can't set internal on fields. So like
computed it is not needed in the schema.
6. added
EntityTestHalJsonInternalPropertyNormalizerTest
10. No this fail if removed. I still need to figure out why @todo figure this out.
14. fixed
16. moved
17. Marked
18. Changed to
$sourceString
Didn't see #98 when I did this. will now address those.
Comment #101
tedbow#98 review
1. fixed
2. 👍
3. 👍
4. fixed
5. 👍
6.
Won't this be a BC break for our REST responses?
Because if we do
That means any computed fields outside of core that are being returned REST responses will all of sudden not be returned.
Comment #102
Wim LeersUgh, you're right :( What a mess. This means we'll have to treat fields and properties inconsistently, for BC reasons. Right?
Comment #103
dawehnerThis looks seriously great!
For some reason this doesn't sound like proper english, anyone may clarify?
🙃 lonely empty line
Comment #104
tedbowYes so I think we have to have all fields be external unless they are explicitly set to internal.
So this patch overrides
isInternal()
in \Drupal\Core\Field\BaseFieldDefinition() to implement thatIt also does this but because
ContentEntityInterface
does not implementComplexDataInterface
which\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::normalizeProperties
expects we have to actually use\Drupal\Core\Entity\Plugin\DataType\EntityAdapter::createFromEntity
.This also means we have to add access logic to
ComplexDataPropertiesNormalizerTrait::normalizeProperties()
to handle field access.It seemed to work the content entities I tested but lets see.
re @dawehner's review in #103
👍
1. fixed
2.
This lines weren't actually lonely, they just like being by themselves sometime but sadly we have to conform to standards... so removed.
Comment #106
damiankloip CreditAttribution: damiankloip at Acquia commentedOverall, very much liking where this is headed! I think it's pretty much where we want to be. Great work pushing this forward Ted. A few comments:
Just to be pedantic, we could cast to a bool here.
This is mainly a preference thing I guess, but we can just ditch the else here. The return value can just be the default return for this method.
non internal?
Howcome we need this? when is the field already there in the test env?
Same here
nit: we can just have one use statement to define multiple here.
This is where I mean access would now not be checked on the property level, compared to the normalizeProperties methods from the new trait.
Hmm, this is introducing access checking we don't currently have. Not sure we should do that here? Question is, does this mean we then need to make the same change for regular iterations outside of this complex data case (the madness we have with some extending classes not implementing ComplexDataInterface)?
Howcome we need this change? This is doing the same as the PrimitiveDataNormalizer I think? Which is meant to be running before this one.
Comment #107
tedbowI made a namespace c/p error in
EntityTestInternalFieldJsonAnonTest
. It will probably still fail with testPatch but should fix the fatal error.ContentEntityNormalizerTest
will fail again b/c it needs to be updated to take in account use ofEntityAdapter
.Comment #109
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedAs we discussed in Vienna, this 'internal' approach makes very much sense! Here's a high-level review for now:
Is this trait really needed? It only saved a few lines of code at the expense of making it harder to scan various classes for what the
isInternal()
method does.I kind of like how every setter and getter stand together in
\Drupal\Core\TypedData\DataDefinition
for example, the code is super easy to read.It would be better if we didn't create an entirely new entity type for these two base fields and add them via
entity_test_entity_base_field_info()
instead.Small contradiction here :)
We don't have any other test data types, so I wonder why is this one special/needed.
It seems to only return some concatenated values after all, which is also not dependent on the
setComputed(TRUE)
definition from the second hunk.Comment #110
Wim LeersThanks for the review, @amateescu! ❤️
\Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestResourceTestBase
, instead of adding lots of new test classes. I like it.Or do you see some better/more elegant way to test this?
Comment #111
tedbow@amateescu thanks for the review and the help in Vienna!
#109
1. Sounds good, removed
2. Ok added this testing to
EntityTestResourceTestBase
which removes the need for a specific test entity type.Also updated
entity_test_entity_base_field_info ()
to add the base field. I wondering though since I am adding the basefield when$entity_type->id() === 'entity_test'
should we just be adding it in\Drupal\entity_test\Entity\EntityTest::baseFieldDefinitions
.Also I am not adding the basefield
non_internal_string_field
anymore because it is just a field thatsetInternal()
is not called on. This the same as all the other basefields.3. Removed this code because not using this basefield anymore see 2)
4. what @Wim Leers said in #110.4
Comment #112
tedbowUnassigning from myself for further reviews. Feel to assign back to me if/when it needs work.
Comment #113
Wim LeersWe shouldn't add it unconditionally in
entity_test_entity_base_field_info()
, but only if\Drupal::state()->get('entity_test.test_internal')
. We'd then set that state in our test coverage. In other words: we should only add this field for our specific test!Comment #115
tedbow@Wim Leers I have tried your suggestion in #113. I am doing something wrong because the column is not being recognized.
If I check for
\Drupal::state()->get('entity_test.test_internal')
I can never get it to work.I always get this error
I have tried running
$this->container->get('state')->set('entity_test.test_internal', TRUE);
at a lot of different points. But nothing works.Comment #117
borisson_I was trying to review this but I don't fully understand what's going on, however there were some small things that could be improved, so attached is patch that fixes my own nits.
Comment #118
Wim Leers@tedbow asked me to look into #115 — some debugging later, ended up with this.
Comment #120
dagmarThis is introducing this concept of isInternal. I think it would be nice to see in the documentation of this method what means to have a internal value. As an example, take a look what ckeditor says about isInternal
Comment #121
Wim Leers#120++
Comment #122
Wim LeersI investigated the failing tests.
First note only tests for the
hal_json
format are failing, those forjson
are passing. That's very interesting.Then, note that #111 removed the custom entity type for testing, in favor of adding a
internal_string_field
field to theEntityTest
entity type. But it did not update the expected normalized entity. Because it's a field that's specifically intended to not be normalized, because it's internal:Or, as @tedbow put it in #104:
Clearly this is working for
json
, but nothal_json
.Comment #123
tedbow@Wim Leers thanks for figuring this out!
Yep this patch has updated
\Drupal\hal\Normalizer\ContentEntityNormalizer
yet so it won't work.This patch fixes that.
The problem with using
\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::normalizeProperties
in\Drupal\hal\Normalizer\ContentEntityNormalizer::normalize
is that it first filters the fields by$context['included_fields']
.We could add the logic inside the trait but I think this is hal only concept.
We could call
normalizeProperties()
properties first and then filter but then we have normalized fields that we won't be returning.So in this patch I have made
\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::getNonInternalAccessibleProperties()
public and added the AccessibleInterface logic into it so that\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::normalizeProperties
can use this directly.which this brings up from #90
So we could add back
TypedDataHelper
and movegetNonInternalAccessibleProperties()
. Since it is called 2 places. But it might to get rid ofgetNonInternalAccessibleProperties()
altogether now. Since\Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait::normalizeProperties
is very thin now and we could just usegetNonInternalAccessibleProperties()
in all the normalizers directly.But not doing that in this patch.
Comment #125
tedbowSo yay now the only test that is not passing is
ContentEntityNormalizerTest
That makes sense because we have changed to use
EntityAdapter
so the expected method calls would be different.Looking at how to mock
EntityAdapter
I was 😦. Then I found\Drupal\Core\Entity\Entity::getTypedData
and I was like 😅.That is much easier to test 🙌.
So this fixes
ContentEntityNormalizerTest
for its current test cases. I am not adding additional test case for checking internal fields. But this will need to be added.Comment #126
dawehnerHere is a naive question: getNonInternal ... is keyed by $name already. Isn't that the same as $field_item->getName() and as such you could array_diff_key it?
Comment #127
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedYay! The huge number of new test classes was exactly the problem :)
This should be only
@return $this
, without any description.What I meant in #109.4 is that the
setComputed()
call here doesn't actually do anything.The value returned by these two properties is solely determined by the fact that they are using a special class,
ComputedStringData
.In other words,
ComputedStringData
andComputedString
are not doing anything special based on their 'computed' definition flag being TRUE or FALSE.Comment #128
dagmarAdded the first attempt to document what I metioned in #120
Comment #129
Wim Leers#123:
Can't this use
array_intersect()
?AFAICT we only had to make this change because we removed access checking (with the
continue
statement) from the normalizer.But was that even necessary?
AFAICT the #123 interdiff could've been much smaller if the
$context['included_fields']
logic was indeed intersected with the list of non-internal fields… but then the access check with thecontinue
statement could've been kept. Which would've meant no changes to the trait, and far fewer changes to the normalizer?#124:
Hahahaha — wonderful use of emojis :D
#126: hah, you made the same remark as I did — I just suggested
array_intersect()
. Whichever one we end up using — I agree that this can be simplified by using one of them.#127: Thanks so much for your continued reviews of this issue — it makes a world of difference to have an Entity/Field API maintainer actively looking at this! ❤️❤️❤️
Yep — good call! 👍
You're right that in the current patch they don't. But in #2910211: Allow computed exposed properties in ComplexData to support cacheability., they will. The need to bubble cacheability metadata from (computed) properties that are being normalized so the final HTTP response can have the necessary associated cacheability metadata was descoped from this issue in #78 and #86 (and so until those comments/patches, those classes did have a clearer reason to exist).
@tedbow, can you roll a patch for #2910211: Allow computed exposed properties in ComplexData to support cacheability. that layers that cacheability bubbling on top of this patch, so that it's once again clear why we are adding those
ComputedString(Data)
classes?#128: 👌 But two nits:
s/procces/process/
ands/calculated/computed
Finally, noticed something else:
🙃😵 — created #2916025: Rename $denormalized to $normalized in \Drupal\hal\Normalizer\FieldItemNormalizer::normalizedFieldValues() to fix this.
Comment #130
Wim LeersComment #131
Wim LeersI also think it's about time that we update the issue summary based on the slightly revised direction since meeting with Entity/Field/Typed Data API maintainers at DrupalCon Vienna two weeks ago.
Comment #132
Wim LeersAlso, how was this
? It's at least.Comment #133
tedbow#127 @amateescu thanks for review!
1. Fixed
2. what @Wim Leers said in #129
#128 @dagmar thanks for getting this started.
Not sure if we should mention "normalization" here because that happens in the Serialization module and this changes in the TypeData system. But not sure how to better explain "internal" in a general way.
Also we should probably use "computed" instead of calculated.
#129
1.
array_intersect_key
instead because the values in$field_items
are not the string field names.I guess this addresses @dawehner's comment in #126
2. Yes we could leave the access check inside
\Drupal\hal\Normalizer\ContentEntityNormalizer::normalize
but why leave it there when the same logic is needed in\Drupal\serialization\Normalizer\ContentEntityNormalizer::normalize
and we can do it in one place.It makes the interdiff bigger but the simpler overall.
Comment #134
tedbowI have started the patch in #2910211: Allow computed exposed properties in ComplexData to support cacheability. which was split off this issue in #78. It seemed simpler not to handle caching in this issue but we still want to be sure our current approach works with caching.
Comment #135
Wim LeersBecause now the trait is doing much more than just normalizing complex data properties. It's no longer about
\Drupal\Core\TypedData\ComplexDataInterface
like its name indicates, but it's also about\Drupal\Core\Access\AccessibleInterface
.It's also refactoring things that are out of scope to refactor here. If we want to share more code, then perhaps it's better to have this access checking logic only in
\Drupal\serialization\Normalizer\ContentEntityNormalizer
and make\Drupal\hal\Normalizer\ContentEntityNormalizer
layer on top of that. Which would also be out of scope here. But it might make things even simpler. Which is my point exactly: by making this change, we open another discussion, about how to simplfy/share the access-checking logic in two different normalizers. That's not what this issue is about.In other words: it's about keeping this patch as simple, focused, in-scope as possible.
#134: yay!
Comment #136
tedbow#135 re
Ok so keeping that in mind. I removed
ComplexDataPropertiesNormalizerTrait
altogether.We were using it in 4 normalizers but basically each really on needed to know which of the properties on the ComplexDataInterface object(or entity which can be converted) that are internal.
Yes that has been in the trait for awhile now so let's remove it. Instead of the trait I added
TypedDataInternalPropertiesHelper
which hasgetNonInternalProperties()
.So now each of the four normalizers are has more code but is changed much less. So these keeps the patch more focused. Also helper class has no logic around AccessibleInterface which the trait did.
Comment #137
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHmm, maybe I couldn't express very well my point from #109.4 and #127.2, so here it is in code form :)
Basically, a computed property doesn't need a specialized data type, it can just use the string one and have a custom class defined, just like
TestProcessed
which will be changed in #2910211: Allow computed exposed properties in ComplexData to support cacheability.. And I don't think we need another level of indirection for concatenating two strings, we can just do it in the main "computed string" class.Comment #138
tedbow@amateescu yep thanks I was getting that. Here is an update patch. Had to rebase for #2916025: Rename $denormalized to $normalized in \Drupal\hal\Normalizer\FieldItemNormalizer::normalizedFieldValues()
Comment #140
tedbowIn #138 somehow by shell script to make patch got messed up because of a need to manualing fix the rebase.
Comment #141
Wim Leers#137: oh wow, I see @tedbow says in #138 he was getting that, but I definitely wasn't — thanks for clarifying!
Reviewed the entire patch again:
❤️
These are all the key changes. And I think they're now A) crystal clear, B) minimal (i.e. zero out of-scope changes). 👍
I think that makes this an order of magnitude easier to review — hopefully subsystem maintainers & committers agree with that :)
(Accuracy) Nit: s/in REST/in the 'hal_json' format/
(Accuracy) Nit: s/in REST/in the 'json' format/
(Clarity) Let's rename this to
entity_test.internal_field
That'd make it clear that this is about testing an internal field. The
EntityTest(Hal)JsonInternalPropertyNormalizerTest
classes are about testing internal properties.Nit: the
@see
belongs on the next line.Supernit: needs blank line above.
⁉️ I still think this should not be necessary.
(Clarity) Nit:
$value_property
(Clarity) Nit: s/internal property/'internal property test'/
(Clarity) Nit: s/Internal/Internal field/
(Clarity) Nit: s/non-internal/non-internal property/
(Clarity) Nit: s/internal/internal property/
Comment #142
tedbow@Wim Leers thanks for the review
1. agreed
2. fixed
3. fixed
4. fixed
5. Fixed
6. super fixed
7. Removed. Yes this may be left over from when the patch had cache tags logic. I removed it locally and ran few tests that involved and it was fine.
8. fixed
9. fixed
10. fixed
11. fixed
12. fixed
Comment #143
Wim LeersThere are only two things holding me back from RTBC'ing now:
/me is rooting furiously that amateescu will RTBC this :)
Comment #144
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedTotally agreed with #141.1 :D
I went through the entire patch as well, and I only found these small problems:
I don't think we should mention the normalization process directly like this in the generic data definition interface. Maybe we could rephrase to something like:
"This can be used in a scenario when it is not desirable to expose this data value to an external system."
Or something along that line :)
'calculated' -> 'computed' ;)
This doesn't seem to be really useful as a separate class, I think it would be better if we just put the
getNonInternalProperties()
inside\Drupal\serialization\Normalizer\ComplexDataNormalizer
because most (all?) the other places that use it extend the complex data normalizer class.And if there aren't any usages from outside the class hierarchy of
ComplexDataNormalizer
, it would probably makes sense for it to not be static anymore.No need for the leading '\' here, we don't namespace procedural code :)
Comment #145
tedbow@amateescu thanks for the review!
1. Ok using your suggested wording.
2.
Removing this altogether because this just the behavior of
\Drupal\Core\TypedData\DataDefinition::isInternal()
But
\Drupal\Core\Field\BaseFieldDefinition::isInternal()
which doesn't extendDataDefinition
doesn't do this.3. Actually there are 4 uses and 1 is
\Drupal\serialization\Normalizer\ComplexDataNormalizer
and 1 extends it. The other 2 in the HAL module don't extend it. So not most. So not removingTypedDataInternalPropertiesHelper
unless you still think it should be inComplexDataNormalizer
4. Fixed.
#143.1 will start on the change record now.
Comment #146
tedbowChange record: https://www.drupal.org/node/2916592
Could use extra eyes on it.
Comment #147
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@tedbow, about #145.3: I would simply put that method in the two classes from
hal
that use it. I don't know.. maybe it's just me, but providing a trait with a single method which contains a simple array filter is just not worth it :)Fixed a few things in the change record: https://www.drupal.org/node/2916592/revisions/view/10679389/10679402
Comment #148
tedbowWell to be precise now it is not a trait it is helper class.
But not only does it contain an
array_filter
it also calls$data->getProperties(TRUE)
to ensure that we bring back computed properties which$data->getProperties()
would not.Also this is code that would immediately need to be duplicated in contrib in JSON API and Schemata module. JSON API is at least on API First roadmap for getting into core.
So changing that right now to get your feedback.
but I did find this problem.
We don't actually use
$context
here. RemovingNever completed this @todo. Adding but in
testNormalize()
Comment #149
mradcliffeI reviewed the change record, and it makes sense. I was able to understand what the change was, why it was made in this way (to keep BC), and how to use it. Nicely done @amateescu and @tedbow for writing it.
Should the TypedDataInternalPropertiesHelper class be marked as @internal? I could see its usage, but it's really basic so I don't think it's that big of a loss if it's an internal class.
Comment #150
Wim LeersNit:
$this->createMockFieldListItem()
should not have default values for its parameters: it’s better to be explicit in tests.Comment #151
tedbowOk I got rid the defaults except for
$user_context
. If I don't put a default ofNULL
and type hint forAccountInterface
then there is error when you pass inNULL
.If we going to change the function signature it seem better to use a type hint where we can.
Comment #152
tedbowre #149 @mradcliffe
Thanks reviewing the change record.
Also for the committer can you make sure @mradcliffe gets credit for this issue. His comment in #36 was key in understanding how contrib modules were already using our normalizers and helped make sure we didn't make a change that broke a bunch of normalizer classes in contrib. Thanks
If others think that is good idea I guess I would be fine with that but I would prefer it to be not internal. It seems useful for others to use.
Comment #153
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLet's leave it up to core committers to decide if
TypedDataInternalPropertiesHelper
should be internal or not. I don't really care *that* much about it to hold this up any longer.Awesome work has been done here, let's get it in!
Comment #155
Wim Leers🎉👌🤞
Comment #156
damiankloip CreditAttribution: damiankloip at Acquia commentedI agree this looks great. I'm totally +1. There are a couple of things left over from my review in #106 that I think got missed in amongst other patches and reviews. They are mostly small things, and some have already been fixed/picked up in subsequent revirews (Like #106.{7-9}).
Leaving as RTBC for now based on that. Thoughts @tedbow @Wim Leers
Comment #157
tedbow@damiankloip glad you are good with RTBC!
So going through all of#106
1. was for
\Drupal\Core\TypedData\DataDefinition::setInternal
We could but setComputed, setRequired, setReadOnly are not doing this. Is there a pattern we follow in general?
2. We are not longer using
InternalDataDefinitionTrait
but\Drupal\Core\TypedData\DataDefinition::isInternal()
no longer has the else like you are asking for.3. Meaning it would be better to say "external" in the test? I prefer "non_internal" because it is easier to find when searching the tests for the "internal" string.
4. The field will not already be there. We can't create the field in
setUp()
becausecreateEntity()
is called inparent::setup()
and we need the field created before we create the entity. But I thinkcreateEntity()
can be called more than once test so we have to haveif
statement to make sure we don't try to create it more than once.5. see 3)
6. Honestly I didn't know you could add multiple traits this way. fixed.
7.
Because we are not using
\Drupal\Core\TypedData\TypedDataInternalPropertiesHelper::getNonInternalProperties
this is where I mean access would now not be checked on the property level, compared to the normalizeProperties methods from the new trait and we don't the trait we are not changing anything anymore about on which the access is being checked at.When we were using the trait I now see that introduced checking access on property level. This kind of change would have been out of scope for this issue.
8. We are not longer doing this.
9. We are no longer changing
TypedDataNormalizer
in the latest patch.So only 1 change for 6) and just a
use
statement.Comment #158
damiankloip CreditAttribution: damiankloip at Acquia commentedGreat - thanks for the response @tedbow. As I mentioned in my previous comment, most of it was already fixed in one way or another, which is nice. All the major things were picked up or implicitly fixed by changes to the direction of the patch, so ++++
RE point 3; I mean that the 'non_internal_value' key has a string of 'Computed! value to be internal'. Should this be 'Computed! value to be non internal' or 'Computed! value to be exposed' ?
Comment #159
Wim Leers#158.3: hah, nice nit!
Comment #160
tedbow@damiankloip duh, I totally missed that. nice catch.
Changed value to be
Comment #161
Wim Leers😀
👍
Back to RTBC per #153!
Comment #162
damiankloip CreditAttribution: damiankloip at Acquia commentedHaha, nice. Thanks @tedbow!
Agree with the RTBC 100% now 👍
Comment #163
larowlanAdding review credits
Comment #164
larowlanCommitted as c74c39e and pushed to 8.5.x.
Published change record.
Comment #165
Wim Leers🎉🙃🍾 After a year of discussing, and nearly half a year of discussing it in this thread, working on a patch, and building towards consensus, this landed! Hurray!
That means #2910211: Allow computed exposed properties in ComplexData to support cacheability. is now unblocked (which was split off from this issue in #86, to make the scope narrower/clearer here). That's now the next blocker, and then we'll finally be able to land #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field, #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs, #2626924: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata and #2577923: MenuLinkContent entities pointing to nodes are not deployable: LinkItem should have a "target_uuid" computed property!
It also means that the https://www.drupal.org/project/jsonapi_extras module no longer needs to have its own mechanism for "disabling" certain fields: they can now just be marked internal. GraphQL would need to do the same. That'd mean that entities exposed via Serialization + REST + JSON API + GraphQL would all be exposed consistently. Created #2921257: On Drupal 8.5, JSON API should respect Typed Data's new ::isInternal(), and JSON API Extras should use ::setInternal() for that. It'll be tricky there, because this new API is only available in 8.5, which is not yet out.
If the core committers would consider committing this to 8.4 too, that'd allow the ecosystem to move along 6 months faster, which would lead to a more mature API-First Drupal considerably faster…
Comment #166
xjmThanks @Wim Leers! @larowlan and I discussed this and we don't think it's really a candidate for backport at this time. Let's focus on making sure all the followups land in 8.5.x and then see where we're at. (Keep in mind that 8.5.0 is less than 4 months away and the alpha deadline is a mere 10 weeks away.)
Comment #167
xjmThe added test from this issue
EntityTestHalJsonInternalPropertyNormalizerTest
seems to be failing nightly on PHP 5.5 & SQLite 3.8 (of all things)? https://www.drupal.org/pift-ci-job/804598Edit: Also postgres and 5.5: https://www.drupal.org/pift-ci-job/804596
I haven't reverted yet because the main environments aren't failing; maybe the version of 5.5 on the other bots is different. We already have other issues with 5.5.9. But let's confirm what's going on to avoid a revert.
Comment #168
Anonymous (not verified) CreditAttribution: Anonymous commented@tacituseu recognized this error like #2919863: Discover why gc_disable() during non-interactive install improves tests stability. I also tested his patch and it works perfect. See #2879048-171: Ignore: patch testing issue for #2919863.
Last time we faced with this error after #2849674: Complex job in ViewExecutable::unserialize() causes data corruption. Looks like issues with 'Complex' word in the title are dangerous :P
Comment #169
Wim Leers#166: thanks for looking into backportability, much appreciate it. There aren't any follow-ups, HEAD is in a good state with this patch being committed. There are lots of super important bugfixes that were blocked on this, and those we will definitely continue working on! But we need one more issue to land for those to be able to move forward: #2910211: Allow computed exposed properties in ComplexData to support cacheability..
#167: 😨😡 It's interesting that those failures are only happening on PHP 5.5 (and on those particular DBs). But … per http://php.net/supported-versions.php, 5.5 is not supported at all, it doesn't even have security support. 5.6 doesn't have this problem. So, I don't know how we're ever going to get to the bottom of this? :(
#168: thanks for those links, for your research, and for that funny association :D
Okay, so @vaplas started to actively debug this in #2879048-169: Ignore: patch testing issue for #2919863 and later. I'm now following that issue and will help out.
Comment #171
Wim Leers#167: the random failure was fixed in #2921862: Segfault on PHP5.5 and PostgreSQL!