Closed (fixed)
Project:
Drupal core
Version:
9.4.x-dev
Component:
serialization.module
Priority:
Critical
Category:
Task
Assigned:
Unassigned
Reporter:
Created:
9 Sep 2021 at 09:18 UTC
Updated:
16 Nov 2021 at 15:24 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
daffie commentedThe problem for adding this return type hint is that a lot of contrib modules override the method. See: http://grep.xnddx.ru/search?text=public+function+normalize%28&filename=. Maybe we need to wait for D10.0 to commit this patch.
Comment #3
catchMost (all?) of these contrib usages are implementations of the interface, not subclasses of core implementations, so I think we should be OK here in 9.x.
Comment #4
larowlanUnion types are PHP 8 only, so I think this is D10 only
Comment #5
daffie commentedFixing the testbot error where
nullis not allowed (lowercase). It must beNULL(uppercase). My only worry is that for the Symfony deprecation testing the uppercase will be allowed.Comment #7
daffie commentedThere are a lot of testbot errors, because the method
normalize()is returning an instance of the classDrupal\jsonapi\Normalizer\Value\CacheableNormalizationinstead of it being an array, string, integer, float, boolean, ArrayObject or null.Comment #8
catchBumping this to critical, that's a problem.
Comment #9
bbralaOk, I thought i'd do a first round of research on this :)
Unfortunately it doesn't only wrap arrays, but also other types like
array|string|int|float|boolThis means we cant just implement ArrayObject and call it a day. Also when looking around it seemcore/modules/jsonapi/src/Serializer/Serializer.phphas not been changed regarding the return type for::normalizeand::denormalizewhich I assume you should in this issue?I've also had a look from where I remembered talk about the normalizer which was (comment in issue #3014283: Use ResourceTypeFields to statically determine the field normalizer by @gabesullice and subsequently @wimleers (he/him)). Where it seems the whole
CacheableNormalizationas a return value has been perhaps not the most sound option.It seems the whole jsonapi normalizing is not really compatible with Symfony, the following doc in the Serializer class in the jsonapi module is a hint also.
Which also has an interesting link with the discussion how this came to be 2923779#comment-12407443 .
This is as far as my research got me right now. I will need more time to start to form some possible solutions. It's a bit late here now.
Comment #10
daffie commentedMaybe an other solution is to ask Symfony if they can change the return type hint to add "object" to the list or change it to "mixed".
Comment #12
bbralaJust pasting a comment by @gabesullice from Slack since it is relevant.
gabesullice 11 hours ago
The CacheableNormalization object has been great IMHO. IIRC, it exposed a couple caching bugs that we were able to fix and it makes it easy to bubble cacheability metadata without weird context gymnastics.
However, it has definitely been difficult to get it to play nicely with Symfony. We are already violating the documented interface. It was only a matter of time before type hints at the language level would break things.
We have gone to extreme lengths to prevent third parties from using the Symfony serialization system to inject custom normalizers into the JSON:API serialization "tree"
With all three of those things in mind, I think we should stop using Symfony serialization stuff instead of trying to become compatible with it.
In that "tree" we could hand off serialization to the Symfony system once we hit the typed data level. Everything "above" that is described by the JSON:API spec and so there's little reason not to hardcode things.
We now have an API for renaming/disabling fields and resource types, which is the only reason I've seen for providing custom normalizers.
tl;dr: providing custom normalizers and different formats is the whole point of using Symfony and jsonapi doesn't need either. (edited)
Comment #13
bbralaI would assume they would want some sort of interface and not a general object. There is actually login the the serializers handling the specific case of ArrayObject (only used to be able to end up with
{}). This would mean more handling there.Although it might be a bit of work, thinking about this a little more yesterday night and this morning in the shower just making sure we adhere to the interface and seeing how much effort it is to make the normalization to the JSON:API member properties not use the Symfony serializer. Not entirely sure of the scope of that path yet though.
Comment #14
catchThanks for digging up the history, that's useful.
I think it's worth asking about #10 - although would not bank on them changing it.
From the original discussion, #2923779-19: Normalizers violate NormalizerInterface:
Diverging further from Symfony's Normalizer in jsonapi would be a way to keep the same implementation, would mean at a minimum forking NormalizerInterface, Normalizer and Serializer, or better just stripping down to what we need to use the JSON:API normalizers.
Wrote this before seeing #12, but that's heading along the same lines.
Comment #15
daffie commentedI have asked Symfony if the return type hint can be changed. See: https://github.com/symfony/symfony/issues/43021
Comment #16
daffie commentedComment #17
bbralaI was thinking, since PHP 8.0 is a think we could work with https://www.php.net/manual/en/class.stringable.php. Easy to implement (i think), close to the truth and would at least allow us to stay close to the interface?
I also thing Stringable SHOULD be part of the interface of the normalizer @ symfony.
What do you guys think?
Comment #18
catchThis crossed my mind briefly, but I forgot to check which PHP version it was introduced in. If it's viable for us, I think it's worth asking Symfony and a lot more likely to get accepted.
Comment #19
bbralaI've proposed it in the linked github issue. But the answer is no. Rationale being:
Pretty hard no it seems
Comment #20
bbralaI've been trying to get a hang on how to attack this problem. My end conclusion after swimming around in the code for an good hour is that the comment by @catch in #14, where he suggested we need to fork a few of the classes in jsonapi, is probably the most realistic way to go about it.
Time is a bit limited today so I probably don't have the time to explore the exact implications of that divergence.
Comment #21
alexpottCouldn't we get around this by making CacheableNormalization extend \ArrayObject - ala https://3v4l.org/b26UN ?
Or can we ask for a new interface that we can return ie. NormalizationInterface that has a getNormalization method?
Comment #22
effulgentsia commentedIn most cases,
CacheableNormalization::$normalizationis an array, soCacheableNormalizationbeing anArrayObjectwould make sense for those cases. In some cases,CacheableNormalization::$normalizationis a scalar. That happens whenFieldItemNormalizerruns for a single-property field. Not sure if it happens for any other code path. For that case, anArrayObjectimplementation wouldn't have sensible behavior. However, if this is just to get around the type hints and if it doesn't get passed to any code outside of jsonapi that will actually invoke any ArrayObject methods, then that might be ok.Comment #23
bbralaIt could be a combination where the last one in the chain working on fielditems would not be the cacheable normalization. This would be in line with what gabesullice mentioned where on that level we just return a string.
Although I'm a little scared of triggering some specific code when implementing arrayobject, there was a reason I discarded that idea earlier. But that might've been triggered by a lack ovellf overview.
My hands are itching to try... but time is not very abundant ATM.
(Sent on mobile)
Comment #24
effulgentsia commentedCurious if this causes any tests to fail.
Comment #25
bbralaI was just opening my editor, got myself an hour. Will try combined with the patch provided in the issue.
Comment #27
bbralaWell you test didn't fail. But it feels so wrong T_T
Comment #28
catchWhat does this mean in terms of:
from #3232074-13: [Symfony 6] Add "array|string|int|float|bool|\ArrayObject|null" to all Normalizer classes that implement the method ::normalize() ?
If this 'just works' as a workaround, then I think it would be OK to commit and then open a follow-up to re-evaluate the serializer/normalizer stuff in JSON:API.
Comment #29
bbralaMy English was a bit broken in that sentence. But what I was afraid of is that if the CachableNormalization at some point gets to the Symfony serializer and gets run though normalize there it could end up in a different branch of logic than it does right now (See Serializer:161). This might end up resulting in unexpected behavior.
But looking at the code right now, if that was happening, it would be throwing an exception since it fall through to the bottom of the method. So I guess it is fine for now.
And yeah, a follow-up is needed to clean this up at some point.
Comment #30
bbralaIf we go this route, we also need to decide, either make it accessible as an array object, or add a comment of some sort to explain it is not to be used as such.
Comment #31
effulgentsia commentedI think we should start by adding a trait that implements all of ArrayObject's methods and throws an exception when any are called.
We can add a followup, and an @todo in the code that links to that followup, to explore whether we then want CacheableNormalization to override any of those methods with an implementation that does something other than throw an exception.
Comment #32
effulgentsia commentedRestoring original issue title and changing component to reflect this issue's scope per #28. The followup issue can be the jsonapi-specific one.
Comment #33
effulgentsia commentedAwesome that #26 passed! NW for #31.
Comment #34
bbralaBit like
Drupal\Component\Utility\DeprecatedArray. There is a case to be made that deprecation is actually correct :x Even though it was never implemented.Comment #35
larowlanAgree
Comment #36
bbralaI'll build up the class tomorrow. Suggestions for the name and exception message are welcome.
Comment #37
larowlanThis will be D10 only because of union types
Comment #38
bbralaContemplating posting this to the Symfony issue. But i'm not yet completely confident in my ability to describe this correctly ;) What do you guys think?
Comment #39
bbralaAfter some slack discussion with @alexpott about this issue i've created a PR for Symfony so we can see if we can fix this upstream by introducing a
NormalizedValueInterface.See symfony/symfony/pull/43498.
That would open up making this abide to the interface and perhaps in the future see if this way of caching makes sense in the other places we use normalization.
Comment #40
bbralaWell, life said nope to this as I needed to go to the doctor with my son. I'm on vacation coming week so small chance i'll find the time to sit behind an editor.
Comment #41
daffie commentedAdded a trait with all the methods that an \ArrayObject implements and all those methods throw an \Exception when called.
I have placed the trait in the same directory as core/modules/jsonapi/src/Normalizer/Value/CacheableNormalization.php. I am not sure if that is the best directory for it. I am also not sure which one would be a better one.
The requested folloup still needs to be created. See comment #31.
Comment #42
bbralaThank you daffie. Few things, shouldn't we just do @inheritdoc?
Also, the message could be a variable, they are all pretty much the same right. :)
Personally I would probably extend a class instead of using a trait but I think that is mostly personal preference. I don't feel strongly about that.
Comment #43
daffie commentedFixed the styleguide violations.
Reply to comment #42: We need to add the throwing of the exception in the docblock and we are not allowed to do:
Comment #44
bbralaAh unfortunate, thanks:)
Comment #45
daffie commentedFixed more style guide violations
Comment #46
daffie commentedFixed more style guide violations
Comment #48
bbralaLooking at the errors, which seem to be errors in how the array object methods are implemented. Most of that is argument type hints it seems.
I think extending a class instead of a trait are a good idea. This would expose all the wrong methods right away instead of when the tests are run.
I've made a patch with this pattern to see how that goes.
Comment #49
bbralaAdded interdiff
Comment #50
daffie commentedAll the code change look good to me.
The class Drupal\jsonapi\Normalizer\Value\CacheableNormalization is now extending \ArrayObject. Therefor it is no longer a problem for Symfony 6.
I have created the requested followup. See: #3245905: Change the ArrayObject methods from Drupal\jsonapi\Normalizer\Value\CacheableNormalization to do something useful.
For me it is RTBC.
Just one question left for the committer:
Comment #51
bbralaPerhaps we could put it at the same place as
DeprecatedArray.But we would like the input of a committer (talked to Daffie on Slack)
Comment #52
catchI think I'd prefer to add this to jsonapi for the moment - since we might come up with a way to do this differently and remove the class altogether again. Once it's in Drupal\Core it's part of the API - we can always move it when we have another use case, but that's easier than having to go through deprecation/removal.
The
DrupalPHP 8.1 failures are introduced by this patch so marking needs work for that.Comment #53
bbralaSure, I'll place it somewhere sane in jsonapi. Also your comment on Drupal 8.1 confused me, but you mean php 8.1 :)
Comment #54
bbralaRenamed class to
Normalizer/Value/TemporaryArrayObjectThrowingExceptionsand fixed a few of the arguments probably. Hopefully it will pass now, i'll wait for that.Comment #55
daffie commentedThe failures are not related with this patch. Back to RTBC.
Comment #56
bbralaThanks :) Requeueing anyways ;)
Comment #58
catchCommitted 9aa1d53 and pushed to 9.4.x. Thanks!
I don't think this has anything we particularly need in 9.3.x, so leaving against 9.4.x
Comment #59
mondrakeThis has broken HEAD testing on PHP 7.3 for the 9.4.x branch, for dbs other than MySql, I am afraid. https://www.drupal.org/node/3060/qa
Comment #60
catchReverted. We should try to resolve the PHP 7.3 issues and re-commit if we can, otherwise this'll end up 10.x-only.
Comment #61
bbralaHmm, that is a bit weird. I'll have a look.
Comment #62
alexpottSerialization changed in 7.3 to 7.4 and we need to allow serialization as this is how \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher::set() works. ON PHP 7.4 and up it is calling the __serialize() method...
Comment #64
bbralaTest are green. I've also checked (just to be sure) if serializing/unserializing this array object gives the proper output on the different php versions. Might not have been needed, but felt like due diligence.
I used the class from the patch and just added a public property.
Anyways, looks fine.
Comment #66
catchThat's very straightforward to avoid the regression, and don't think we should worry about adding back those exceptions in 10.0.x - better to see what else we can come up with that doesn't involved ArrayObject at all.
Committed 5c78c18 and pushed to 9.4.x. Thanks!