Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Serializer] Instantiator - Add an interface and default implementation to instantiate objects #30956

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

Closed
wants to merge 3 commits into from

Conversation

joelwurtz
Copy link
Contributor

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

Related to #30818, Replace #30925

This add a new interface and default implementation to instantiate objects

OBJECT_TO_POPULATE was not keep here, as i don't think it should be the responsability of an instantiator to handle that. And if we want to have this responsability we can always add a new implementation with a decoration system.

I try to look at var exporter instantiator also and unfortunetaly @nicolas-grekas i cannot use this, since the behavior of this component is to not use the constructor, in the serializer we want to use it, or at least it used to do that and we cannot change this behavior.

But we can use var exporter implementation in a future PR (will not be the default however just another way of doing it) here if someone does not want to call the constructor.

@javiereguiluz
Copy link
Member

Probably out of scope and irrelevant but ... we recently added the VarExporter component which already includes an Instantiator. Are we duplicating efforts here? Thanks.

@joelwurtz
Copy link
Contributor Author

@javiereguiluz Like i said on top, i was aware of that, but unfortunetaly we cannot use it as a default implementation since the var export implementation try to skip the constructor where we want to use it here, as it's the current behaviour of existing normalizers.

Like If someone was using constructor to set default values for its class, using the var exporter would break its behaviour.

One thing that would be possible however is to create a Denormalizer that use the Var Exporter Component, but it's out of scope for this PR.

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

@pounard pounard Aug 7, 2019

Choose a reason for hiding this comment

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

In case of missing constructor arguments, you probably should fall back on using the property access component to arbitrarily set properties onto the object. Problem is that your code change with time, but serialized entities stored somewhere don't.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just forget the previous comment, sorry for the noise. But it raise an interesting question: the instanciator sets some values using the constructor, then the API using it (for example, the normalizer because it's today the only API using it I know of) will set some others, it seems like each component responsibility is unclear, they both walk on each other foot.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instantiator will only set values related to constructor, if there is other data to set, it will be done after by the ObjectNormalizer

Copy link
Contributor

Choose a reason for hiding this comment

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

How will the normalizer now which properties have already been set or not if the instanciator becomes an opaque interface ? It seems that it will then be forced to re-inject all properties ? Maybe not since you pass an array reference, you can remove constructor-set values from the array, but it's a very weird interface then, you have two different outputs: the object, and the modified array. It's not cognitively-easy. I know do understand your answer below thought.

Copy link
Contributor

Choose a reason for hiding this comment

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

Related to #30956 (comment)

Actually we don't have a simpler way to pass both object and new data array, so we use the reference to handle the data. It's probably not the best way to do it but it can be improved.
I think we should more focus on code duplication in Serializer before focusing on attended behavior even if they do use references.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I finally understood why you're doing it this way now. This is weird because the instanciator and the normalizer then share some bits of internal logic then, whereas having an external interface main benefit is "divide and conquer", making every component simpler and avoid cognitive surcharge when reading bare algorithm (also make it simpler to maintain). I agree this is not an easy one, thanks for taking the time to answer.

@dunglas
Copy link
Member

dunglas commented Jan 22, 2020

@joelwurtz do you think you'll be able to finish this PR anytime soon?

@Korbeil Korbeil force-pushed the feature/instantiator branch 3 times, most recently from 0c08336 to d327945 Compare February 2, 2020 20:39
Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

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

Thank you for this PR.

I've got some comments and questions.

return $this->error;
}

public function hasFailed(): bool
Copy link
Member

Choose a reason for hiding this comment

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

Is this method really needed?
Is it failed when it doesn't have an object or when it does not have an error?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is failed when the $object property is null. It's used in https://github.com/symfony/symfony/pull/38487/files#diff-cd90b97b50abf20fa94468689e7486afR315-R317 to know when we need to throw an Exception or not. We could make the condition directly but I think it's better DX to make it a class method.

return $this->context;
}

public function getError(): ?string
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this return a Throwable instead?

@@ -306,6 +312,7 @@ protected function prepareForDenormalization($data)
}

/**
<<<<<<< HEAD
Copy link
Member

Choose a reason for hiding this comment

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

Oops =)


protected function getConstructor(\ReflectionClass $reflectionClass): ?\ReflectionMethod
{
return $reflectionClass->getConstructor();
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't the context hold information what other method to call?

That means that we can also declare this class as final and we dont need to use inheritance like this.

@@ -118,6 +125,26 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
}
$this->classDiscriminatorResolver = $classDiscriminatorResolver;
$this->objectClassResolver = $objectClassResolver;

$this->childContextFactory = $childContextFactory ?? new ObjectChildContextFactory();
Copy link
Member

Choose a reason for hiding this comment

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

Why not pass the $childContextFactory to parent::__construct()?

*/
class StaticConstructorNormalizer extends ObjectNormalizer
class StaticConstructorInstantiator extends Instantiator
Copy link
Member

Choose a reason for hiding this comment

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

Let's try to find a way to to support static constructors without extending Instantiator

@fabpot
Copy link
Member

fabpot commented Oct 7, 2020

We've just moved away from master as the main branch to use 5.x instead. Unfortunately, I cannot reopen the PR and change the target branch to 5.x. Can you open a new PR referencing this one to not loose the discussion? Thank you for your understanding and for your help.

Korbeil pushed a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 8, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 9, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 11, 2020
Korbeil pushed a commit to Korbeil/symfony that referenced this pull request Oct 12, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 12, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 12, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 16, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 16, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 16, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 17, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 20, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 20, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 20, 2020
Korbeil added a commit to Korbeil/symfony that referenced this pull request Oct 20, 2020
@joelwurtz joelwurtz deleted the feature/instantiator branch August 14, 2023 20:13
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.