Skip to content

[Serializer] Object class resolver #28669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

alanpoulain
Copy link
Contributor

@alanpoulain alanpoulain commented Oct 1, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets
License MIT
Doc PR

When normalizing an object, it could be useful to use a custom method to resolve the class name of it instead of using get_class.
For instance, Doctrine is using proxy classes for lazy-loading and we usually want the real class and not the proxied one.
That's why we are using this trait in API Platform: https://github.com/api-platform/core/blob/master/src/Util/ClassInfoTrait.php
With this feature, we could solve an issue in API Platform with the JSON-LD normalizer when the eager fetching is disabled.

@@ -50,7 +51,12 @@
*/
protected $classDiscriminatorResolver;

public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null)
/**
* @var ObjectClassResolverInterface|null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we make it a callable and remove the interface instead?

/**
* @var ObjectClassResolverInterface|null
*/
protected $objectClassResolver;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the docblock can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't it be useful to have it in the child classes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's still catchable by overriding the constructor in child classes
but we don't want to promise BC regarding any changes that could happen after instantiation.

@nicolas-grekas nicolas-grekas added this to the next milestone Oct 1, 2018
/**
* @var ObjectClassResolverInterface|null
*/
protected $objectClassResolver;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the docblock can be removed

*/
protected $objectClassResolver;

public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ObjectClassResolverInterface $objectClassResolver = null)
Copy link
Member

@dunglas dunglas Oct 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you switch to a callable, you can just set '\get_class' as default value, then you can remove the ternary operator later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although that might be slower

@alanpoulain
Copy link
Contributor Author

I have used a callable instead of an interface.
For the sake of performance, I have kept the ternary.

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(with minor comment)

@@ -86,7 +88,7 @@ public function normalize($object, $format = null, array $context = array())
$data = array();
$stack = array();
$attributes = $this->getAttributes($object, $format, $context);
$class = \get_class($object);
$class = $this->objectClassResolver ? \call_user_func($this->objectClassResolver, $object) : \get_class($object);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be written as ($this->objectClassResolver)($object)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've copied what was done for the maxDepthHandler.

$attributeValue = \call_user_func($this->maxDepthHandler, $attributeValue, $object, $attribute, $format, $context);

It seems to me there is no important difference between the two ways of doing it: https://stackoverflow.com/questions/1596221/php-call-user-func-vs-just-calling-function/35031466

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

($this->objectClassResolver)($object) is more elegant, but indeed being consistent is good

@dunglas dunglas force-pushed the serializer-object-class-resolver branch from eb5d8d4 to 18d2143 Compare October 3, 2018 20:34
@dunglas
Copy link
Member

dunglas commented Oct 3, 2018

Thanks @alanpoulain for working on this feature, this is much appreciated.

@dunglas dunglas merged commit 18d2143 into symfony:master Oct 3, 2018
dunglas added a commit that referenced this pull request Oct 3, 2018
This PR was squashed before being merged into the 4.2-dev branch (closes #28669).

Discussion
----------

[Serializer] Object class resolver

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

When normalizing an object, it could be useful to use a custom method to resolve the class name of it instead of using `get_class`.
For instance, Doctrine is using proxy classes for lazy-loading and we usually want the real class and not the proxied one.
That's why we are using this trait in API Platform: https://github.com/api-platform/core/blob/master/src/Util/ClassInfoTrait.php
With this feature, we could solve an issue in API Platform with the JSON-LD normalizer when the eager fetching is disabled.

Commits
-------

18d2143 [Serializer] Object class resolver
@nicolas-grekas nicolas-grekas modified the milestones: next, 4.2 Nov 1, 2018
This was referenced Nov 3, 2018
@@ -60,6 +61,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
$classDiscriminatorResolver = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
}
$this->classDiscriminatorResolver = $classDiscriminatorResolver;
$this->objectClassResolver = $objectClassResolver;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's late, but how about $this->objectClassResolver = $objectClassResolver >: '\get_class'; or something like that ? So that we don't need to test if it is not null on each call

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Taluu : You should provide a PR :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants