-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[VarExporter] add Instantiator::instantiate() to create+populate objects without calling their constructor nor any other methods #28417
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
Conversation
b157f1d
to
702fb4d
Compare
use Symfony\Component\VarExporter\Internal\Registry; | ||
|
||
/** | ||
* A utility class to create objects without calling their constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most likely a good idea to keep this @internal
until proven to be stable for usage within the component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposed method borrows from the existing infrastructure in the component: if there are any issues with Instantiator::instantiate()
, it's also an issue for VarExporter::export()
. It would make no sense to mark this method as internal, and not the other.
And if we mark the other internal, then the component would have zero public interfaces. It wouldn't make sense :)
* // creates an empty instance of Foo | ||
* Instantiator::instantiate([Foo::class => []]); | ||
* | ||
* // creates a Foo instance and sets one of its public, protected or private properties |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public
/private
/protected
case should likely be always distinguished, because of inheritance and possible property overrides.
* Instantiator::instantiate([Foo::class => ['propertyName' => $propertyValue]]); | ||
* | ||
* // creates a Foo instance and sets a private property defined on its parent Bar class | ||
* Instantiator::instantiate([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This syntax is very confusing: the object type should be the first string
parameter. What's the reasoning behind putting the first type inside the array?
* Instances of ArrayObject, ArrayIterator and SplObjectHash can be created | ||
* by using the special "\0" property name to define their internal value: | ||
* | ||
* // creates an SplObjectHash where $info1 is attached to $obj1, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strongly suggest keeping anything coming from php core or extensions out of the scope of the component, as well as its documentation: there's an infinity of wrongness in classed that are not defined in PHP userland, and we really should rather tell users to not rely on them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above: this is already dealt with for VarExporter::export()
, so any issue found there should be spotted and fixed. There are already tests for these btw (which doesn't mean it's yet bulletproof of course.)
Thanks for the detailed description! Looking at the end-user API, I have two comments/questions:
// Before
Instantiator::instantiate([
Foo::class => [],
Bar::class => ['propertyName' => $propertyValue],
]);
// After
Instantiator::instantiate(Foo::class, []);
Instantiator::instantiate(Bar::class, ['propertyName' => $propertyValue]);
// Before
Instantiator::instantiate([SplObjectStorage::class => ["\0" => [$obj1, $info1, $obj2, $info2]]]);
Instantiator::instantiate([ArrayObject::class => ["\0" => $inputArray]]);
// After
Instantiator::instantiate(SplObjectStorage::class, [$obj1 => $info1, $obj2 => $info2]);
Instantiator::instantiate(ArrayObject::class, $inputArray); |
702fb4d
to
3465299
Compare
3465299
to
6eacb76
Compare
@Ocramius @javiereguiluz thanks for the review+comments, I addressed some inline, let's address the remaining ones: I added a 2nd commit to change the signature to
It's not actually: e.g. you can also add dynamic properties to ArrayObject instances, that are not stored in the internal array structure. The This doesn't mean we cannot do what you propose @javiereguiluz, but we still need a way to set dynamic properties when needed. Actually, there is a trivial way that works already: using an So, let's say we want to create something like that using instantiate: $a = new ArrayObject($input);
$a->foo = 123; The current code allows doing so using: Instantiator::instantiate(ArrayObject::class, ['foo' => 123, "\0" => [$input]]); And we could make it do the same using: Instantiator::instantiate(ArrayObject::class, [$input], ['stdClass' => ['foo' => 123]]); Please advise. |
Thanks for the detailed explanation. I hadn't thought about this use case: $a = new ArrayObject($input);
$a->foo = 123; I thought this utility was only for instantiating ... but that code instantiates and then initializes, so it's doing two different things :) What if we use this signature?
Then, this example would look as: Instantiator::instantiate(ArrayObject::class, [$input], ['foo' => 123]) But most of the times you'd simply use something like this: Instantiator::instantiate(ArrayObject::class, [$input]) |
@javiereguiluz wouldn't make sense: the specific property of instantiators is to not call the constructor nor any other methods... |
I just followed your last example: why pass |
After talking with Nico on Slack about this ... it's clear to me that I don't fully understand this 😅 So let's forget about my previous examples and proposals. Sorry! |
6eacb76
to
b2308d7
Compare
b2308d7
to
127a2bd
Compare
Good news: shaking the code always helps to spot new edge cases. |
5b16b53
to
c18f056
Compare
This PR was merged into the 4.2-dev branch. Discussion ---------- [VarExporter] fix more edge cases | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - As found while preparing #28417 Commits ------- 443bd11 [VarExporter] fix more edge cases
c18f056
to
77376e4
Compare
Fixes split in PR #28437. This PR is now rebased on top of it. |
|
||
class InstantiatorTest extends TestCase | ||
{ | ||
use VarDumperTestTrait; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure im missing it.. but this can be removed right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, removed thanks
…cts without calling their constructor nor any other methods
77376e4
to
d9bade0
Compare
@nicolas-grekas We needs good docs on that one (and the whole component). Can you create a PR for that in the docs repo? I think it won't take too much of your time as you're the best to write something smart about this :) |
Thank you @nicolas-grekas. |
…e+populate objects without calling their constructor nor any other methods (nicolas-grekas) This PR was merged into the 4.2-dev branch. Discussion ---------- [VarExporter] add Instantiator::instantiate() to create+populate objects without calling their constructor nor any other methods | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - A blend of features also provided by https://github.com/doctrine/instantiator and https://github.com/Ocramius/GeneratedHydrator in one simple method. Because it's just a few more lines on top of the existing code infrastructure in the component :) For example, from the docblock: ```php // creates an empty instance of Foo Instantiator::instantiate(Foo::class); // creates a Foo instance and sets one of its public, protected or private properties Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); // creates a Foo instance and sets a private property defined on its parent Bar class Instantiator::instantiate(Foo::class, [], [ Bar::class => ['privateBarProperty' => $propertyValue], ]); ``` Instances of ArrayObject, ArrayIterator and SplObjectHash can be created by using the special `"\0"` property name to define their internal value: ```php // creates an SplObjectHash where $info1 is attached to $obj1, etc. Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); // creates an ArrayObject populated with $inputArray Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray, $optionalFlag]]); ``` Misses some tests for now, but reuses the existing code infrastructure used to "unserialize" objects. Commits ------- d9bade0 [VarExporter] add Instantiator::instantiate() to create+populate objects without calling their constructor nor any other methods
A blend of features also provided by https://github.com/doctrine/instantiator and https://github.com/Ocramius/GeneratedHydrator in one simple method. Because it's just a few more lines on top of the existing code infrastructure in the component :)
For example, from the docblock:
Instances of ArrayObject, ArrayIterator and SplObjectHash can be created
by using the special
"\0"
property name to define their internal value:Misses some tests for now, but reuses the existing code infrastructure used to "unserialize" objects.