Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Serializer] Argument objects #19277

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
merged 14 commits into from
Jul 11, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Address comments
  • Loading branch information
theofidry committed Jul 3, 2016
commit e64e999a39ba3ee1a6c46560bedd22e32358c257
45 changes: 31 additions & 14 deletions 45 src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ protected function getConstructor(array &$data, $class, array &$context, \Reflec
return $reflectionClass->getConstructor();
}

/**
* @see instantiateComplexObject
* @deprecated Since 3.1, will be removed in 4.0. Use instantiateComplexObject instead.
Copy link
Member

Choose a reason for hiding this comment

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

missing @trigger_error() call for this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xabbuh done

*/
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
{
return $this->instantiateComplexObject($data, $class, $context, $reflectionClass, $allowedAttributes);
}

/**
* Instantiates an object using constructor parameters when needed.
*
Expand All @@ -287,7 +296,7 @@ protected function getConstructor(array &$data, $class, array &$context, \Reflec
*
* @throws RuntimeException
*/
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, $format = null)
protected function instantiateComplexObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, $format = null)
Copy link
Member

Choose a reason for hiding this comment

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

It can now be:

instantiateComplexObject(array &$data, $class, $format, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)

To mimic the SerializerInterface::deserialize prototype.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my bad I kept the old signature, will change it

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about using instantiateClass to have something shorter but still clear ?

{
if (
isset($context[static::OBJECT_TO_POPULATE]) &&
Expand Down Expand Up @@ -319,33 +328,41 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref

$params = array_merge($params, $data[$paramName]);
}
Copy link
Member

Choose a reason for hiding this comment

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

Why removing the unset?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bad copy/paste, should be kept

} elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {

continue;
}

if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
$parameterData = $data[$key];
if (null !== $constructorParameter->getClass()) {
Copy link
Contributor

@GuilhemN GuilhemN Jul 5, 2016

Choose a reason for hiding this comment

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

ReflectionParameter::getType() should be used when available imo and exceptions catched in case the type hinted class doesn't exist.

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 personally find it easier to check check if the class is null or not in this case

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree but the type hint may be on a class not loadable. In this case, the exception thrown should be catched and the default value applied.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

could you elaborate? On which scenario may it happen? Not that I'm not trusting you, but if that's the case I would like to add a test for it :)

Copy link
Contributor

Choose a reason for hiding this comment

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

In case you use a type hint with a class from a third party library which is not loaded because you don't need it for example.
And the advantage of getType() is that it doesn't need to load the class, so would work anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, fair enough will change that then

$parameterData = $this->serializer->denormalize($parameterData, $constructorParameter->getClass()->getName(), null, $context);
$parameterData = $this->serializer->deserialize($parameterData, $constructorParameter->getClass()->getName(), null, $context);
Copy link
Member

Choose a reason for hiding this comment

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

$format instead of null

}

// Don't run set for a parameter passed to the constructor
$params[] = $parameterData;
unset($data[$key]);
} elseif ($constructorParameter->isDefaultValueAvailable()) {

continue;
}
Copy link
Contributor

@GuilhemN GuilhemN Jul 6, 2016

Choose a reason for hiding this comment

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

Should not be updated imo as this is less clear...

Copy link
Contributor Author

@theofidry theofidry Jul 6, 2016

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

@GuilhemN GuilhemN Jul 6, 2016

Choose a reason for hiding this comment

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

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Edit: BTW, we could reverse the condition if ($constructor) to have less lines indented as you said :)

I like this one :)

Copy link
Contributor

Choose a reason for hiding this comment

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

But not sure this would be accepted as this is a small detail and could create many merge conflicts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.


if ($constructorParameter->isDefaultValueAvailable()) {
$params[] = $constructorParameter->getDefaultValue();
} else {
throw new RuntimeException(
sprintf(
'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.',
$class,
$constructorParameter->name
)
);
}

throw new RuntimeException(
sprintf(
'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.',
$class,
$constructorParameter->name
)
);
}

if ($constructor->isConstructor()) {
return $reflectionClass->newInstanceArgs($params);
} else {
return $constructor->invokeArgs(null, $params);
}

return $constructor->invokeArgs(null, $params);
}

return new $class();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
$normalizedData = $this->prepareForDenormalization($data);

$reflectionClass = new \ReflectionClass($class);
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
$object = $this->instantiateComplexObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);

foreach ($normalizedData as $attribute => $value) {
if ($this->nameConverter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
$normalizedData = $this->prepareForDenormalization($data);

$reflectionClass = new \ReflectionClass($class);
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
$object = $this->instantiateComplexObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunatelly this looks like a bc break as people may depend on the call to instantiateObject.

Copy link
Contributor Author

@theofidry theofidry Jul 6, 2016

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

@GuilhemN GuilhemN Jul 6, 2016

Choose a reason for hiding this comment

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

In case someone extends instantiateObject it won't be called anymore. I'll provide you an example of how this could be managed.

Copy link
Contributor

@GuilhemN GuilhemN Jul 6, 2016

Choose a reason for hiding this comment

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

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(...);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

taking $format as the 6th argument in instantiateObject would still be a problem if you extended it

Copy link
Member

Choose a reason for hiding this comment

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

This is forbidden by the PHP interpreter itself: https://3v4l.org/S48gf
Si there is no problem here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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).

Copy link
Member

@dunglas dunglas Jul 10, 2016

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

This solution is often used in symfony so i'd say it's not part of the symfony bc promises.


$classMethods = get_class_methods($object);
foreach ($normalizedData as $attribute => $value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,13 @@ public function testConstructorWithObjectDenormalize()

public function testConstructorWithObjectTypeHintDenormalize()
{
$data = [
$data = array(
'id' => 10,
'inner' => [
'inner' => array(
'foo' => 'oof',
'bar' => 'rab',
],
];
),
);

$obj = $this->normalizer->denormalize($data, DummyWithConstructorObject::class);
$this->assertInstanceOf(DummyWithConstructorObject::class, $obj);
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.