[Serializer] Argument objects#19277
Conversation
| } elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { | ||
| $params[] = $data[$key]; | ||
| // don't run set for a parameter passed to the constructor | ||
| unset($data[$key]); |
There was a problem hiding this comment.
bad copy/paste, should be kept
|
This case was omitted when adding deserialization support for complex relations (in 3.1). IMO it should be merged in 3.1. Can you add a test for this? |
| * @throws RuntimeException | ||
| */ | ||
| protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes) | ||
| protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, $format = null) |
There was a problem hiding this comment.
Sorry I realized that this is a BC break... It should be removed. But it's annoying because some normalizers may depend of the format (normalizers of API Platform for instance).
An alternative approach:
- Rename this method
instantiateComplexObject(with the new parameter, it can be movec before$contextfor consistency) - Add back
instantiateObjectwith the current signature and callinstantiateComplexObjectinside it with format asnull - Deprecate
instantiateObject
WDYT?
|
Hm, looks like this also occurs for setters but this is a bigger problem: the serializer leave the set part to the property accessor, so it doesn't have access to the mutator and can't guess the type. |
7b86d12 to
e64e999
Compare
| * @throws RuntimeException | ||
| */ | ||
| protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes) | ||
| protected function instantiateComplexObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, $format = null) |
There was a problem hiding this comment.
It can now be:
instantiateComplexObject(array &$data, $class, $format, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)To mimic the SerializerInterface::deserialize prototype.
There was a problem hiding this comment.
my bad I kept the old signature, will change it
There was a problem hiding this comment.
What do you think about using instantiateClass to have something shorter but still clear ?
|
@dunglas not sure where the problem lies right now, will have to dig a bit into it later although I hope there won't be any big change. |
|
@dunglas tests are passing |
| } | ||
|
|
||
| /** | ||
| * @see instantiateComplexObject |
There was a problem hiding this comment.
missing @trigger_error() call for this
| * @deprecated Since 3.1, will be removed in 4.0. Use instantiateComplexObject instead. | ||
| */ | ||
| protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes) | ||
| { |
There was a problem hiding this comment.
since version 3.2 and @trigger_error(sprintf('...'), E_USER_DEPRECATED);
There was a problem hiding this comment.
I'm quite surprised this went undetected by the phpunit bridge...
There was a problem hiding this comment.
@theofidry phpunit bridge only discovers deprecated errors, not general silenced errors.
There was a problem hiding this comment.
@wouterj but I have a test with the @legacy group, so I guess I expected it to warn me or throw me an error if no deprecation notice were discovered
|
Updated |
|
|
||
| if ($constructor->isConstructor()) { | ||
| return $reflectionClass->newInstanceArgs($params); | ||
| } else { |
There was a problem hiding this comment.
Should not be updated imo as this is less clear...
There was a problem hiding this comment.
The more indent you have the harder it is to read. And if the choice is to use if/else/elseif blocks, then everything should be in an else instead of the return done L311.
There was a problem hiding this comment.
There is only one line here and i agree that most of the time else +return is only redundant but here it highlights the fact that one of this line will be executed and that the second one is not just a default.
Edit: BTW, we could reverse the condition if ($constructor) to have less lines indented as you said :)
There was a problem hiding this comment.
Edit: BTW, we could reverse the condition if ($constructor) to have less lines indented as you said :)
I like this one :)
There was a problem hiding this comment.
But not sure this would be accepted as this is a small detail and could create many merge conflicts.
There was a problem hiding this comment.
Yeah actually I think I'll revert everything for that part, it's just CS and although I think it makes a big difference it terms of readability, I fear that it will cause lots of conflicts later on. One solution would be do go back in earlier versions and fix that version after version, but it's rather tedious and slow.
|
|
||
| $reflectionClass = new \ReflectionClass($class); | ||
| $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes); | ||
| $object = $this->instantiateComplexObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format); |
There was a problem hiding this comment.
Unfortunatelly this looks like a bc break as people may depend on the call to instantiateObject.
There was a problem hiding this comment.
Not sure to see what you mean, before it was simply failing so people could not used it. The method has been changed because there's a BC break in the method signature (we are adding $format), but the behaviour is the same as the previous one.
There was a problem hiding this comment.
In case someone extends instantiateObject it won't be called anymore. I'll provide you an example of how this could be managed.
There was a problem hiding this comment.
You could do something like:
protected function instantiateObject(..., $allowedAttributes/**, $format*/)
{
$format = null;
if (func_num_args() >= 6) {
$format = func_get_arg(5);
}
// ...
}
protected function instantiateComplexObject(...)
{
static $deprecationTriggered = false;
if (false === $deprecationTriggered) {
$deprecationTriggered = true;
$class = get_class($this);
if (0 !== strpos($class, 'Symfony\\')
&& self::class !== (new \ReflectionMethod($this, 'instantiateObject'))->getDeclaringClass()->getName()) {
@trigger_error(..., E_USER_DEPRECATED);
}
}
return $this->instantiateObject(...);
}There was a problem hiding this comment.
taking $format as the 6th argument in instantiateObject would still be a problem if you extended it
There was a problem hiding this comment.
This is forbidden by the PHP interpreter itself: https://3v4l.org/S48gf
Si there is no problem here.
There was a problem hiding this comment.
When you override a protected method, you must respect its contract... It's not a BC break for me.
If you override it this way there is not BC as the added param is optional.
In all honesty I would be happy to say yes to this solution it's the easiest way to deal with it really. The only thing I'm worried about is that it may introduce a BC break (but that's up to you to decide if it is or not) and the fact that the user may not see the change (i.e. see that he has to inject the format now).
There was a problem hiding this comment.
Anyway, it's not a BC break because the parent function is still called with the good number of arguments. See https://3v4l.org/XpYGK.
There was a problem hiding this comment.
Nvm, even with my example it wouldn't be a problem as you would call the parent function with the right number of parameters. Not doing so is looking for trouble and would be the developer fault.
There was a problem hiding this comment.
This solution is often used in symfony so i'd say it's not part of the symfony bc promises.
Prevent BC in instantiateObject
|
Status: Reviewed 👍 when tests for PHP <7 will be fixed |
|
Thank you @theofidry. |
This PR was merged into the 3.2-dev branch.
Discussion
----------
[Serializer] Argument objects
| Q | A
| ------------- | ---
| Branch? | 3.1
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | TODO
| Fixed tickets | none
| License | MIT
| Doc PR | TODO
Assuming with have the two following entities:
```php
namespace AppBundle\Entity;
class Dummy
{
public function __construct(int $id, string $name, string $email, AnotherDummy $anotherDummy)
{
$this->id = $id;
$this->name = $name;
$this->email = $email;
$this->anotherDummy = $anotherDummy;
}
}
class AnotherDummy
{
public function __construct(int $id, string $uuid, bool $isEnabled)
{
$this->id = $id;
$this->uuid = $uuid;
$this->isEnabled = $isEnabled;
}
}
```
Doing the following will fail:
```php
$serializer->denormalize(
[
'id' => $i,
'name' => 'dummy',
'email' => 'du@ex.com',
'another_dummy' => [
'id' => 1000 + $i,
'uuid' => 'azerty',
'is_enabled' => true,
],
],
\AppBundle\Entity\Dummy::class
);
```
with a type error, because the 4th argument passed to `Dummy::__construct()` will be an array. The following patch checks if the type of the argument is an object, and if it is tries to denormalize that object as well.
I'm not sure if it's me missing something or this is a use case that has been omitted (willingly or not), but if it's a valuable patch I would be happy to work on finishing it.
Commits
-------
988eba1 fix tests
98bcb91 Merge pull request #1 from dunglas/theofidry-feature/param-object
7b5d55d Prevent BC in instantiateObject
e437e04 fix reflection type
3fe9802 revert CS
5556fa5 fix
d4cdb00 fix CS
93608dc Add deprecation message
f46a176 Apply patch
f361e52 fix tests
4884a2e f1
e64e999 Address comments
e99a90b Add tests
7bd4ac5 Test
|
You're welcome ;) |
This PR was merged into the 3.2 branch. Discussion ---------- [Serializer] Fix argument object denormalization | Q | A | ------------- | --- | Branch? | 3.2 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #20670 | License | MIT | Doc PR | N/A Fixes #20670. I've seen #19277 (diff) so I think it's the right thing to do, but I didn't follow the thread at the time, so I may have missed something. Ping @theofidry, @dunglas. Commits ------- 27de65a [Serializer] Fix argument object denormalization
Assuming with have the two following entities:
Doing the following will fail:
with a type error, because the 4th argument passed to
Dummy::__construct()will be an array. The following patch checks if the type of the argument is an object, and if it is tries to denormalize that object as well.I'm not sure if it's me missing something or this is a use case that has been omitted (willingly or not), but if it's a valuable patch I would be happy to work on finishing it.