[Serializer] Fix is/has/can accessor naming to strip prefix unless colliding#63136
Conversation
5e4bc35 to
dd55e65
Compare
…ng collision detection Before PR symfony#61097 (Symfony 6.4.26/7.3.5), methods like isPublished() would serialize using the base name "published" by default. PR symfony#61097 fixed an issue where objects with both $isPublished property and isPublished() method couldn't round-trip properly. However, it caused a regression: ALL is/has/can accessors started using the prefixed form ("isPublished") when a matching property exists, even when there's no actual collision. This PR fixes the regression by: 1. Only using the full method name (e.g., "isPublished") when there's an actual collision - another property or accessor that would map to the same base name ("published") 2. Keeping the base name ("published") when no collision exists, matching pre-6.4.26 behavior for the common case 3. Extracting shared collision detection logic into AccessorCollisionResolverTrait to ensure consistent behavior between ObjectNormalizer and AttributeLoader
dd55e65 to
44d25a4
Compare
|
@nicolas-grekas Thanks for iterating over this, besides from #63187 to me it looks like this is the way to go. Now de- and serialization should behave deterministically, also fixing the initial problem (#61097 (comment)). |
| }; | ||
|
|
||
| // ctype_lower check to find out if method looks like accessor but actually is not, e.g. hash, cancel | ||
| if (null === $i || ctype_lower($methodName[$i] ?? 'a') || $method->isStatic()) { |
There was a problem hiding this comment.
@nicolas-grekas is there a reason for ignoring static methods here? This introduces a regression when using serialization groups with static methods (a use case for this when there are different implementations for an interface and more details about the implementation are available via a static method)
There was a problem hiding this comment.
please submit a PR to remove that limitation, this case wasn't covered by a test and I didn't think it was use.
| } elseif ($this->hasProperty($reflMethod->getDeclaringClass(), $name)) { | ||
| $attributeName = $this->getAttributeNameFromAccessor($reflClass, $reflMethod, false); | ||
|
|
||
| if ($this->hasPropertyForAccessor($reflMethod->getDeclaringClass(), $name) && (null === $attributeName || $this->hasAttributeNameCollision($reflClass, $attributeName, $name))) { |
There was a problem hiding this comment.
@nicolas-grekas with that, static methods like public static function select() will be passed to normalization which is probably something we don't want to?
There was a problem hiding this comment.
I'd be happy to review a failing test case
…s (digilist) This PR was merged into the 6.4 branch. Discussion ---------- [Serializer] Normalize static methods when they have groups | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | https://github.com/symfony/symfony/pull/63136/files#r2761176730 | License | MIT PR #63136 introduced a regression that stopped normalization of static methods when they had explicit groups defined. Before that changes, static methods with groups were normalized. Afterwards, these static methods are not normalized anymore. The regression was caused because previously `ObjectNormalizer::extractAttributes()` ignored static methods while `AttributeLoader::loadClassMetadata()` did not, and the PR introduced a new `getAttributeNameFromAccessor()` method that is shared across those two methods. As a result the `AttributeLoader::loadClassMetadata()` method is now also ignoring static methods. You can verify this regression by adding the test case from this PR to a commit before the PR was merged (e.g. `1db906a6802c54f8a28f79fbb928d5cb1d5a642c^`) Commits ------- 78c509c [Serializer] Normalize static methods when they have groups
Before PR #61097 (Symfony 6.4.26/7.3.5), methods like
isPublished()would serialize using the base name "published" by default.PR #61097 fixed an issue where objects with both
$isPublishedproperty andisPublished()method couldn't round-trip properly. However, it caused a regression: ALL is/has/can accessors started using the prefixed form ("isPublished") when a matching property exists, even when there's no actual collision.This PR fixes the regression by:
AccessorCollisionResolverTraitto ensure consistent behavior betweenObjectNormalizerandAttributeLoader