Hi internals,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided independently.
The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).
The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.
What do people think about adding such an alias? Is this worthwhile?
Regards,
Nikita
Hi Nikita, Rowan,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided
independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
Yes, please! A future where dynamic properties are only allowed on
DynamicObject is bright.
I would even deprecate the stdClass alias straight away, so that the whole
thing can be gone in PHP 9.
Kind regards,
Benjamin
Am 06.09.2021 um 18:08 schrieb Benjamin Morel benjamin.morel@gmail.com:
Yes, please! A future where dynamic properties are only allowed on
DynamicObject is bright.
I have nothing against a DynamicObject alias for people who like to be more explicit in their code...
I would even deprecate the stdClass alias straight away, so that the whole
thing can be gone in PHP 9.
... but I'd prefer retaining a backward compatible version for library code, at least for a while and preferably without warnings :-)
- Chris
Hi Nikita,
I think this might be a good idea, but I would like to propose yet another
variant.
Replace stdClass with DynamicObject and keep stdClass as an alias. It can
be deprecated in 8.3.
If we only add an alias, I am afraid that it will not catch on quickly
enough. What I am proposing is that the cast to object will create
DynamicObject by default.
$arr = [1,2];
var_dump((object) $arr);
Output:
object(DynamicObject)#1 (2) {
["0"]=>
int(1)
["1"]=>
int(2)
}
It will break unit tests and it might break some code (e.g. if ('stdClass' === $class)
), but it will help people understand what is the preferred
name going forward without deprecating it right now.
Regards,
Kamil
Hi Nikita,
I think this might be a good idea, but I would like to propose yet another
variant.
Replace stdClass with DynamicObject and keep stdClass as an alias. It can
be deprecated in 8.3.
If we only add an alias, I am afraid that it will not catch on quickly
enough. What I am proposing is that the cast to object will create
DynamicObject by default.$arr = [1,2];
var_dump((object) $arr);
Output:
object(DynamicObject)#1 (2) {
["0"]=>
int(1)
["1"]=>
int(2)
}It will break unit tests and it might break some code (e.g.
if ('stdClass' === $class)
), but it will help people understand what is the preferred
name going forward without deprecating it right now.
My only apprehension with making stdClass rather than DynamicObject the
alias is the widespread impact this will have on extension testing code.
https://github.com/php/php-src/pull/7475 shows the approximate impact on
php-src. We need to update references to stdClass in var_dump()
output in
more than 300 tests. We can do this easily, but it will be an inconvenience
for 3rd party extension tests that need to deal with more than one PHP
version.
Apart from that I agree that making DynamicObject the actual name and
stdClass the alias would be better.
Regards,
Nikita
Hi Nikita,
I think this might be a good idea, but I would like to propose yet another
variant.
Replace stdClass with DynamicObject and keep stdClass as an alias. It can
be deprecated in 8.3.
If we only add an alias, I am afraid that it will not catch on quickly
enough. What I am proposing is that the cast to object will create
DynamicObject by default.$arr = [1,2];
var_dump((object) $arr);
Output:
object(DynamicObject)#1 (2) {
["0"]=>
int(1)
["1"]=>
int(2)
}It will break unit tests and it might break some code (e.g.
if ('stdClass' === $class)
), but it will help people understand what is the preferred
name going forward without deprecating it right now.My only apprehension with making stdClass rather than DynamicObject the
alias is the widespread impact this will have on extension testing code.
https://github.com/php/php-src/pull/7475 shows the approximate impact on
php-src. We need to update references to stdClass invar_dump()
output in
more than 300 tests. We can do this easily, but it will be an inconvenience
for 3rd party extension tests that need to deal with more than one PHP
version.Apart from that I agree that making DynamicObject the actual name and
stdClass the alias would be better.Regards,
Nikita
Adding the alias, I'm fully on board with.
Removing stdClass any time before PHP 10, I'm not on board with. The user space breakage potential is too large to even be thinking about that at this point.
Flipping the alias around... again, I'm very concerned about the BC breakage. Apparently readonly
caused problems for Wordpress, which is doubleplusungood. I would assume that we can't safely change which is the real name until proven otherwise, and based on Nikita's comments about PHP's own tests, I'd say it's definitely not proven otherwise.
--Larry Garfield
I think this would be a massive benefit to first-time PHP users one or two years from now.
I remember being confused by this terminology — I am sure bugs have been caused by people who assumed stdClass was a base class for all objects.
Best wishes,
Matt
Den 2021-09-06 kl. 17:28, skrev Nikita Popov:
Hi internals,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
Regards,
Nikita
Well, we have legacy code where stdClass is quite prevalent and the code
works just fine!
In case of deprecation of stdClass I would like to point out that the
benefit of replacing "new stdClass" with "new DynamicObject" in our
existing code is absolutely zero. However, using DynamicObject in new
code has a benefit since the intent is clearer.
I think one also should take into account how much is stdClass described
in documentation and books. Reason is that deprecation is not just about
how it affects code. I'm a bit of a book worm so I'll check up on how
the usage of stdClass is described in some PHP books.
If deprecation of stdClass is on the table I think it should target the
last 8.x or 9.0 zero release.
r//Björn L
In case of deprecation of stdClass I would like to point out that the
benefit of replacing "new stdClass" with "new DynamicObject" in our
existing code is absolutely zero. However, using DynamicObject in new
code has a benefit since the intent is clearer.
Just to mildly challenge this: Why is the intent of old code any less
important? Do you never hire junior developers, or those cross-training
from other languages, who have to maintain that code? Do you never have
days where your brain is just a bit fuzzy, and every extra meaning you
have to think about is one less line of code you'll get written?
But yes, for experienced PHP developers, the name is not that big a
deal. Indeed, a lot of PHP developers probably never even see it. It's
one of those little "paper cuts", where if we can make the fix not too
painful (including deprecation messages not being too annoying), it will
pay off in the long run.
Regards,
--
Rowan Tommins
[IMSoP]
Hi internals,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
I do like the idea very much as "stdClass" is most confusion and also wrong.
The name "DynamicObject" is much better but I'm not sure if this is a
good name either.
-
It's a class -> Why do we suffix it with "Object"?
-
Yes it's about dynamic properties - but is this the user goal to have
dynamic properties or is it an implementation detail to get something
else (map/dict)?
Wouldn't it be better to name it for what purpose it's being used
(map/dict/ordered dict/...) instead of how this is done? And if we want
to go that route we could also add common functionalities to it like
getting list of keys/values converting to array iterating etc. it's
already possible right know by casting to array but it would be more
logically.
Yes we have assoc arrays in PHP to serve the purpose but this also has
it's downsides as there is no type for it and the issue of converting an
empty array from/to JSON (is it a list or a map?).
The big difference, of course, is the by-ref vs. by-value.
Hope that's not stupid questions?
Greetings,
Marc
Hi Marc,
The name seems to be ok IMHO. Even though it is a class, the main use case
is to denote an object that was created dynamically rather than through an
instantiation of a class. And I know that a lot of usage comes from people
directly creating new instances of stdClass but if you do that then you can
use proper classes also (with Nikita's proposal to deprecate dynamic
properties one would have to use a class with defined properties to do the
same).
It's not unheard that a class has "object" in the name. e.g.
https://docs.oracle.com/javaee/7/api/javax/json/JsonObject.html or
https://docs.microsoft.com/en-us/dotnet/api/system.json.jsonobject?view=dotnet-plat-ext-5.0
The name DynamicObject is also used in other languages.
https://docs.microsoft.com/en-us/dotnet/api/system.dynamic.dynamicobject?view=net-5.0
The word "Dynamic" semantically explains the purpose of the instance of
such class: it has no fixed structure.
We could call it Map or Dictionary, which would also be fitting, but in
doing so we are opening up the scope of this RFC. How much more
functionality does PHP need? What would be the goal of it? PHP has tons of
functions for working with arrays. If we introduce another type like Map
then it would be expected that a set of methods be added to it. This sounds
similar to scalar objects. And while it would be nice to have such kind of
functionality in PHP, it would also be a lot of work to implement it. If we
already have associative arrays, do we really need another structure to do
the same thing? I think it would be prudent to first understand why people
are using stdClass in the first place. From my experience, most of the time
people who work with stdClass don't expect it to have any methods. It's
just a container for data just like an array, but with a different syntax.
It's not much more different than an anonymous class.
The goal of Nikita's proposal is to retain the functionality of dynamically
assigning properties to an object after deprecating dynamic properties on
all other objects. The new class DynamicObject would be a special type of
class that would retain this functionality. Thus, the name fits perfectly.
A DynamicObject is a class whose properties can be created dynamically.
Regards,
Kamil
- It's a class -> Why do we suffix it with "Object"?
Classes are generally named to make sense when you have an instance: a
class called "HttpRequest" is not saying that the class is a request,
but that every instance is one, as in "$myRequest = new HttpRequest;"
Similarly, "DynamicObject" is not saying that the class is an object
(or dynamic, for that matter), but that every instance is: "$myObject =
new DynamicObject;"
- Yes it's about dynamic properties - but is this the user goal to
have dynamic properties or is it an implementation detail to get
something else (map/dict)?
I think a lot of uses of stdClass are precisely where people feel a map
or dictionary isn't appropriate, and that's why they don't want to use
an array: for instance, decoding a JSON string, or a database result
row. The values aren't all of the same type, and you would never want to
apply the same operation to all of them; you will probably initialise
them once, and then access them by name, but don't have the facility to
declare them up front.
Regards,
--
Rowan Tommins
[IMSoP]
Hi internals,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
Regards,
Nikita
If the alias goes to vote as the name DynamicObject, then I will
likely abstain. I don't care.
If it goes to vote under some other name, perhaps "map", or "dict",
then I would likely oppose it. I would like to keep those names
available for more useful features. There are multiple feature that
would like these names:
- Hacklang-style dict, which is a value-based dictionary type.
Importantly, this is copy-on-write like an array; it does not have
object semantics. - In the event we ever add generics, it would be nice to have these
names available for interfaces.
I don't think an alias and replacement name for stdClass is important
enough to use these names.
Hi internals,
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
So I am trying to get around what this would actually mean in practice, and what the ramifications would be.
Given this proposal would (object)array()
return an object of class 'DynamicObject' instead of 'stdClass'?
Will I be able to do a new DynamicObject
just like I can currently do new stdClass
? I assume that instance would have a class of 'DynamicObject'?
And what of new stdClass
? Would get_class(new stdClass)==='DynamicObject'
or would it continue to be get_class(new stdClass)==='stdClass'
Or would this be just like any class we can define today but that would still support dynamic properties whereas all other classes would disallow dynamic properties per the other proposal?
What about standard library functions that return a stdClass
object? (I assume there has to be at least one but I am not certain there is.). Assuming there are standard functions that return objects, will they now return 'DynamicObject' or still 'stdClass'?
===========
So while trying to get my head around the ramifications I prepared this bit of introspection, which is still missing some applicable tests I am sure:
https://3v4l.org/MDlOn#v8.0.10 https://3v4l.org/MDlOn#v8.0.10
===========
What I am struggling with here is to understand the vision for this change. Not that I find the change a bad idea — I do think it intimates a good direction — but at least as presented if feels like the vision for this may not have been fleshed out, or at least not fully communicated.
Maybe what I am looking for is a vision for what this change would mean for future PHP. Is it just a special-cases that will cause as much confusion for developers trying to understand why there are too names for the same thing as it will help those who will better understand the new name, or is it more fully addressing the use-case for why people use dynamic objects?
Maybe that's where we should start. I know there are some people who (I think) strongly argue that we should never use non-typed objects ever again but why do people use dynamic objects, still? This is a legitimate question, not a rhetorical, snide or sarcastic one.
===========
So what are the use-cases where dynamic objects still make sense, and just as importantly what about those use-cases is still suboptimal besides just the class name? (I can think of a few, but want to hear from others before I explain mine.)
-Mike
So I am trying to get around what this would actually mean in practice, and what the ramifications would be.
The proposal is that the new name is an "alias" - the same class, but
with two names. So the following would all create exactly the same object:
$obj = new stdClass;
$obj = new DynamicObject;
$obj = (object)[];
The following would be true for all of them:
assert( $obj instanceOf stdClass );
assert( $obj instanceOf DynamicObject );
There are some places that have to return some "real" name for the
class, like get_class($obj). The current proposal is that "stdClass" be
kept as the real name, at least initially, so you would see this:
$obj = new DynamicObject;
echo get_class($obj); // stdClass
So while trying to get my head around the ramifications I prepared
this bit of introspection, which is still missing some applicable
tests I am sure:
You can get a better idea of what's being proposed using the
class_alias()
function, which lets you create an alias for a
user-defined class: https://3v4l.org/a4EI0#v8.0.10
Is it just a special-cases that will cause as much confusion for developers trying to understand why there are too names for the same thing as it will help those who will better understand the new name
That is certainly a risk, and a factor in favour of having some plan to
retire the old name, even if only after a long overlap.
or is it more fully addressing the use-case for why people use dynamic objects?
The thread comes from a comment I made in Nikita's discussion about
deprecating dynamic properties on normal objects. There was no grand
vision behind it, just a long-standing dislike of the name "stdClass",
and a concern that documenting "extends stdClass" as a way to enable
dynamic properties on a custom class would make the confusion worse.
So what are the use-cases where dynamic objects still make sense, and just as importantly what about those use-cases is still suboptimal besides just the class name?
To be honest, I think most uses of stdClass would be better served by
anonymous classes, and would have been written that way if the feature
had existed for longer. The one thing missing from anonymous classes
right now is an elegant way to capture data from outer scope when you
create them.
Regards,
--
Rowan Tommins
[IMSoP]
Is it just a special-cases that will cause as much confusion for developers trying to understand why there are too names for the same thing as it will help those who will better understand the new name
That is certainly a risk, and a factor in favour of having some plan to retire the old name, even if only after a long overlap.
Ironically I believe if we add DynamicObject
as an alias of stdClass
where DynamicObject::class === 'stdClass'
that would not provide much ability to retire stdClass
any sooner than if we just deprecated it today. All future code that needs to refer to the class name will still refer to stdClass
, so we won't be gaining much by creating an alias.
OTOH if we introduce a completely new class named DynamicObject
having exactly the same behavior as a stdClass
object (at least initialize) then we could add any new behavior to DynamicObject
and leave stdClass
fully in-tact for as long as we need to, except for notes in the docs that say to move to DynamicObject
and tools like PhpStorm, Psalm and Phan could start suggesting a move away from stdClass
right away.
With a new independent class 'DynamicObject' === get_class( new DynamicObject )
would always be true, and people who want to future proof their code could start replacing stdClass
with DynamicObject. People that don't (want to) make the change could maybe get a warning in PHP 9 and then maybe we remove
stdClass` in PHP and they must evolve or stick with PHP 8.
#fwiw I think DynamicObject
is the best semantic name floated thus far albeit sadly on the rather long side. DynamicClass
, also long, could work too...
========
Of course having a different class begs the question of what get_class((object)array())
should return? Clearly for BC it needs to be stdClass
, but as long as it is stdClass
then we get no real benefit from aliasing with DynamicObject.
Further, although the expression style of (object)['prop' => 'value']
is the simplest way to initialize a basic object
with literals in a single expression it is still a bad workaround that is far from intuitive for new developers, and feels like we are just leveraging an accidental capability rather than a well-designed language feature.
If we want DynamicObject
to eventually replace stdClass
why not ALSO provide new syntax to give people a reason to switch to it? Why not leverage the named constructor concept but as a special case for DynamicObject
, and only for DynamicObject
(not stdClass
); allow any arbitrary names to be used in constructor promotion? Instead of this:
$obj = (object)array(
"foo" => 1,
"bar" => "hello",
"baz" => true,
);o
We could do this instead:
$obj = new DynamicObject(
foo: 1,
bar: "hello",
baz: true,
);
AND since we are talking a singular special case, why not also add a DynamicObject()
function to streamline it a bit:
$obj = DynamicObject(
foo: 1,
bar: "hello",
baz: true,
);
OR we could get inspired by the shortening of function()
to fn()
and used obj() (since do()
is probably unworkable):
$obj = obj(
foo: 1,
bar: "hello",
baz: true,
);
OR my personal favorite (though I know that would be a bridge too far for many) why not just this, too?
$obj = {
foo: 1,
bar: "hello",
baz: true,
};
While we are at it, DynamicObject could add a few really useful methods such as fromArray()
, toJSON()
and more? (And if I really want to get crazy, toSqlUpdate()
, toSqlInsert()
, etc, but now I fear I am just scaring people off.)
If we gave a better developer experience with literal initializers and useful methods then we've give a lot of developers reason to update their code to use DynamicObject
and thus we could deprecate stdClass
sooner.
A couple more things; add a JSON_OUTPUT_DYNAMIC_OBJECT
flag to output to DynamicObject for json_decode()
, add a 3rd parameter for flags to var_export()
for the same reason, a 'return_dynamic_object'
option for unserialize()
, and so on.
(BTW, we don't have to do ALL these things in one RFC. But we could go ahead and discuss the potential of having future RFCs that would add these features.)
or is it more fully addressing the use-case for why people use dynamic objects?
The thread comes from a comment I made in Nikita's discussion about deprecating dynamic properties on normal objects. There was no grand vision behind it, just a long-standing dislike of the name "stdClass", and a concern that documenting "extends stdClass" as a way to enable dynamic properties on a custom class would make the confusion worse.
Yes, I followed that discussion. But the fact you had a discussion doesn't ensure that the proposed solution is the proper one. Hence why I was trying to illicit a discussion about what would be more proper.
The one thing missing from anonymous classes right now is an elegant way to capture data from outer scope when you create them.
I'm not exactly sure what that means, but if I had to guess I think maybe you are talking about a literally initialization syntax which is something I covered above?
-Mike
P.S.
There are some places that have to return some "real" name for the class, like get_class($obj). The current proposal is that "stdClass" be kept as the real name, at least initially, so you would see this:
$obj = new DynamicObject;
echo get_class($obj); // stdClass
Yes, and that is where I see it to be most problematic, although admittedly for a built-in class with no predefined methods or properties the places where it could break an application are certainly many fewer than aliases for userland classes. But I digress on this latter point...
You can get a better idea of what's being proposed using the
class_alias()
function, which lets you create an alias for a user-defined class: https://3v4l.org/a4EI0#v8.0.10
Thanks for educating me on the existence of class_alias()
; 12+ years of professional PHP and I can't remember ever noticing that existed.
If it didn't exist I would challenge why we need it, especially because it breaks the boolean which I had thought was true, that $class_name === get_class( new $class_name() ). But since it does exist me arguing that would be moot.
So what are the use-cases where dynamic objects still make sense, and just as importantly what about those use-cases is still suboptimal besides just the class name?
To be honest, I think most uses of stdClass would be better served by anonymous classes, and would have been written that way if the feature had existed for longer.
Hmm. That seems like replacing apples with oranges.
I assume we would also disallow dynamic properties in anonymous classes too, right? After all, they are just statically declared classes that the developer do not assign a name.
Anonymous class objects have different strengths and weaknesses and thus are useful in different contexts than dynamic objects.
My guess would be that the name reported by get_class etc would mainly be used in tests and debugging, whereas instanceOf checks (and type constraints) will be used in actual "business logic" code paths. Existing code will expect "instanceOf stdClass" to work, and new code will expect "instanceOf DynamicObject" to work, so an alias (in either direction) seems the better compromise.
Besides, don't forget stdClass::class
in addition to get_class()
.
Anyway, with respect, I'd like to hear use-cases from others who are the ones saying they are using a lot of stdClass objects in their existing code. They may have insight that neither you nor I have on the topic.
A couple more things; add a
JSON_OUTPUT_DYNAMIC_OBJECT
flag to output to
DynamicObject forjson_decode()
, add a 3rd parameter for flags to
var_export()
for the same reason, a'return_dynamic_object'
option for
unserialize()
, and so on.
It would also be interesting to enter a user-defined class here to work as
a reversed JsonSerializable. Could be a static factory method for all I
care, and would omit the requirement of serialization libraries for
"simple" things where you still want decent object typing. Could also work
together with interfaces accepting properties, effectively making the
result of json_decode an anonymous class that still adheres to an
interface. In case of the custom 'deserialization' you can throw exceptions
if the format is not correct. In the case of an anonymous class with an
interface it could throw an exception if the structure doesn't match
(possibly controlled by flags?).
A couple more things; add a
JSON_OUTPUT_DYNAMIC_OBJECT
flag to output to
DynamicObject forjson_decode()
, add a 3rd parameter for flags to
var_export()
for the same reason, a'return_dynamic_object'
option for
unserialize()
, and so on.It would also be interesting to enter a user-defined class here to work as
a reversed JsonSerializable. Could be a static factory method for all I
care, and would omit the requirement of serialization libraries for
"simple" things where you still want decent object typing. Could also work
together with interfaces accepting properties, effectively making the
result of json_decode an anonymous class that still adheres to an
interface. In case of the custom 'deserialization' you can throw exceptions
if the format is not correct. In the case of an anonymous class with an
interface it could throw an exception if the structure doesn't match
(possibly controlled by flags?).
I think we want round-trips of json_decode()
+ json_encode()
to work
loss-free, without any custom classes or extra parameters.
Currently, '{}' and '[]' have different results with json_decode()
.
assert(json_encode(json_decode('{}')) === '{}'); // Perfect round-trip.
assert(json_encode(json_decode('{}', JSON_OBJECT_AS_ARRAY)) === '[]');
// Lossy round-trip.
This can only work if we have a built-in data transfer object,
distinct from arrays.
Currently this is \stdClass, using the dynamic property mechanism.
But one could easily imagine a different kind of data transfer object,
which would use a different mechanism instead of dynamic properties.
A native implementation of JsonSerializable does not really work here,
because ->jsonSerialize() would have to return \stdClass to result in
'{}'.
Instead, what about something with a "->getData(): array" method, but
then json_decode()
would encode it as '{}'?
assert(json_decode('{}') instanceof DataTransferObject);
assert(json_decode('{}')->getData() === []);
assert(json_encode(json_decode('{}')->getData()) === '[]');
assert(json_encode(json_decode('{}')) === '{}');
For the proposed rename:
- If we can fully deprecate and replace dynamic properties long-term,
I would rather keep the name \stdClass until it dies. - Instead of an alias or rename, I would rather have a new data
transfer object class which would not rely on dynamic properties, but
on a new mechanism.
Imo, creating an alias won't actually make life easier for anyone,
unless we can fully remove and replace \stdClass.
It would mean that developers need to learn both names, and understand
how aliases work - something you don't really need to learn otherwise.
Think of the silly wtfs that can occur if people don't fully
understand aliases, or are not aware that DynamicObject is just an
alias, while \ReflectionClass and get_class()
still return 'stdClass'
as the class name.
-- Andreas
Wow!
I notice that ArrayObject already does everything we would need for
json_encode()
.
assert(json_encode(new ArrayObject([5])) === '{"0":5}');
However, it does so in a very strange way, not using any of the public
methods, but also not using dynamic properties.
It seems there is a hard-coded internal implementation when calling
json_encode()
or converting to array.
https://3v4l.org/rAc4K
I think we would want something more clean and transparent.
A couple more things; add a
JSON_OUTPUT_DYNAMIC_OBJECT
flag to output to
DynamicObject forjson_decode()
, add a 3rd parameter for flags to
var_export()
for the same reason, a'return_dynamic_object'
option for
unserialize()
, and so on.It would also be interesting to enter a user-defined class here to work as
a reversed JsonSerializable. Could be a static factory method for all I
care, and would omit the requirement of serialization libraries for
"simple" things where you still want decent object typing. Could also work
together with interfaces accepting properties, effectively making the
result of json_decode an anonymous class that still adheres to an
interface. In case of the custom 'deserialization' you can throw exceptions
if the format is not correct. In the case of an anonymous class with an
interface it could throw an exception if the structure doesn't match
(possibly controlled by flags?).I think we want round-trips of
json_decode()
+json_encode()
to work
loss-free, without any custom classes or extra parameters.Currently, '{}' and '[]' have different results with
json_decode()
.assert(json_encode(json_decode('{}')) === '{}'); // Perfect round-trip.
assert(json_encode(json_decode('{}', JSON_OBJECT_AS_ARRAY)) === '[]');
// Lossy round-trip.This can only work if we have a built-in data transfer object,
distinct from arrays.
Currently this is \stdClass, using the dynamic property mechanism.But one could easily imagine a different kind of data transfer object,
which would use a different mechanism instead of dynamic properties.A native implementation of JsonSerializable does not really work here,
because ->jsonSerialize() would have to return \stdClass to result in
'{}'.
Instead, what about something with a "->getData(): array" method, but
thenjson_decode()
would encode it as '{}'?assert(json_decode('{}') instanceof DataTransferObject);
assert(json_decode('{}')->getData() === []);
assert(json_encode(json_decode('{}')->getData()) === '[]');
assert(json_encode(json_decode('{}')) === '{}');For the proposed rename:
- If we can fully deprecate and replace dynamic properties long-term,
I would rather keep the name \stdClass until it dies.- Instead of an alias or rename, I would rather have a new data
transfer object class which would not rely on dynamic properties, but
on a new mechanism.Imo, creating an alias won't actually make life easier for anyone,
unless we can fully remove and replace \stdClass.
It would mean that developers need to learn both names, and understand
how aliases work - something you don't really need to learn otherwise.
Think of the silly wtfs that can occur if people don't fully
understand aliases, or are not aware that DynamicObject is just an
alias, while \ReflectionClass andget_class()
still return 'stdClass'
as the class name.-- Andreas
All future code that needs to refer to the class name will still refer to
stdClass
, so we won't be gaining much by creating an alias.
Just to be clear, the only code that would need to change is code that
dynamically gets out the class name, from get_class()
, var_export()
,
reflection, and the like. Using the class name in code, like "$foo = new
stdClass;" and "$foo instanceof stdClass", would carry on working just
fine, whichever way we defined the alias.
Besides, don't forget
stdClass::class
in addition toget_class()
.
The ::class syntax is purely string replacement (apart from some rare
edge cases), and works identically whether the class exists, is an
alias, or doesn't exist at all.
I assume we would also disallow dynamic properties in anonymous classes too, right? After all, they are just statically declared classes that the developer do not assign a name.
The difference I see is that stdClass/DynamicObject allows you to add or
remove properties from an object after it has been created. I think a
lot of use cases don't actually need that, and would benefit from error
messages when doing so accidentally.
You mentioned short-hand syntaxes like this:
$obj = {
foo: 1,
bar: "hello",
baz: true,
};
I would love for that, or some other short-hand, to be equivalent to this:
$obj = new class(foo: 1, bar: "hello", baz: true) {
public $foo;
public $bar;
public $baz;
public function __construct($foo, $bar, $baz) {
$this->foo = $foo;
$this->bar = $bar;
$this->baz = $baz;
}
}
That is, an anonymous class, with exactly those three properties. If you
also want to be able to define extra properties after it's created,
you could opt into that using whatever mechanism a named class would
(parent class, trait, attribute, etc; see other thread).
Similarly, the objects created by json_decode or PDO_FETCH_OBJECT only
need the initial properties to be dynamic, not to allow properties to
be added later.
Regards,
--
Rowan Tommins
[IMSoP]
All future code that needs to refer to the class name will still refer to
stdClass
, so we won't be gaining much by creating an alias.Just to be clear, the only code that would need to change is code that
dynamically gets out the class name, fromget_class()
,var_export()
,
reflection, and the like. Using the class name in code, like "$foo = new
stdClass;" and "$foo instanceof stdClass", would carry on working just
fine, whichever way we defined the alias.Besides, don't forget
stdClass::class
in addition toget_class()
.The ::class syntax is purely string replacement (apart from some rare
edge cases), and works identically whether the class exists, is an
alias, or doesn't exist at all.I assume we would also disallow dynamic properties in anonymous classes too, right? After all, they are just statically declared classes that the developer do not assign a name.
The difference I see is that stdClass/DynamicObject allows you to add or
remove properties from an object after it has been created. I think a
lot of use cases don't actually need that, and would benefit from error
messages when doing so accidentally.You mentioned short-hand syntaxes like this:
$obj = {
foo: 1,
bar: "hello",
baz: true,
};I would love for that, or some other short-hand, to be equivalent to this:
$obj = new class(foo: 1, bar: "hello", baz: true) {
public $foo;
public $bar;
public $baz;
public function __construct($foo, $bar, $baz) {
$this->foo = $foo;
$this->bar = $bar;
$this->baz = $baz;
}
}That is, an anonymous class, with exactly those three properties. If you
also want to be able to define extra properties after it's created,
you could opt into that using whatever mechanism a named class would
(parent class, trait, attribute, etc; see other thread).Similarly, the objects created by json_decode or PDO_FETCH_OBJECT only
need the initial properties to be dynamic, not to allow properties to
be added later.
Even if no properties can be added after construction, this would
still mean that the list of properties can be determined at run-time.
There are two ways this can work:
- Every new instance has its own anonymous class, even if they were
created by the same statement in code. - Different instances created by the same statement in code have the
same anonymous class, but this class supports dynamic properties.
Btw now that I think of it, I have seen lots of code where objects
from PDO are modified post construction, also with new properties
being added.
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
I assume we would also disallow dynamic properties in anonymous classes too, right? After all, they are just statically declared classes that the developer do not assign a name.
The difference I see is that stdClass/DynamicObject allows you to add or remove properties from an object after it has been created. I think a lot of use cases don't actually need that, and would benefit from error messages when doing so accidentally.
The concern is not were they are not needed, but instead where they are needed. Such as when loading JSON from a source that does not ensure the schema is 100% stable over time.
That's also why I was hoping some of those complaining about deprecating stdClass would explain explain their use-cases as they may know of use-cases we are not considering.
You mentioned short-hand syntaxes like this:
$obj = {
foo: 1,
bar: "hello",
baz: true,
};I would love for that, or some other short-hand, to be equivalent to this:
$obj = new class(foo: 1, bar: "hello", baz: true) {
public $foo;
public $bar;
public $baz;
public function __construct($foo, $bar, $baz) {
$this->foo = $foo;
$this->bar = $bar;
$this->baz = $baz;
}
}That is, an anonymous class, with exactly those three properties. If you also want to be able to define extra properties after it's created, you could opt into that using whatever mechanism a named class would (parent class, trait, attribute, etc; see other thread).
Exactly!
Actually the same functionality for named classes is something I have wanted since literally the very first day I starting using PHP so I wouldn't have to run userland code that loops through an array that runs for so many object instantiations. I have to believe it would be more performant if in C. (Reasons I do that? Loading from JSON, XML and/or SQL.). We've need either a magic method or ability to pass a close for name translations and short circuiting.
That functionality is the primary reason I almost always use a base class; it would be nice to get rid of that requirement.
Similarly, the objects created by json_decode or PDO_FETCH_OBJECT only need the initial properties to be dynamic, not to allow properties to be added later.
How do you define "initial?"
-Mike
In the thread for deprecation of dynamic properties, Rowan suggested that
we alias "stdClass" to "DynamicObject" in
https://externals.io/message/115800#115802. I wanted to split this
discussion off into a separate thread, as this can be decided
independently.The rationale for this is that "stdClass" is something of a misnomer: The
name makes it sound like this is a common base class, as used in a number
of other languages. However, PHP does not have a common base class. The
only way in which "stdClass" is "standard" is that it is the return type of
casting an array to (object).The actual role of stdClass is to serve as a container for dynamic
properties, thus the suggested DynamicObject name.What do people think about adding such an alias? Is this worthwhile?
Hi Nikita, Rowan,
I'm reading the discussion about the side of the alias. Can't we solve
these concerns by making DynamicObject extend stdClass instead of aliasing?
That wouldn't allow an stdClass object to get through the DynamicObject
typehint, but that shouldn't be an issue since no such code has yet been
written, isn't it?
Nicolas
I'm reading the discussion about the side of the alias. Can't we solve
these concerns by making DynamicObject extend stdClass instead of
aliasing? That wouldn't allow an stdClass object to get through the
DynamicObject typehint, but that shouldn't be an issue since no such
code has yet been written, isn't it?
I've been pondering inheritance options, but I don't think they really
help - whichever approach we use, one of these assertions will be false:
assert( new DynamicObject instanceof stdClass ); // false if stdClass is
a sub-class of DynamicObject
assert( new stdClass instanceof DynamicObject ); // false if
DynamicObject is a sub-class of stdClass
assert( get_class(new stdClass) === 'stdClass' ); // false if stdClass
is an alias of DynamicObject
assert( get_class(new DynamicObject) === 'DynamicObject' ); // false if
DynamicObject is an alias of stdClass
My guess would be that the name reported by get_class etc would mainly
be used in tests and debugging, whereas instanceOf checks (and type
constraints) will be used in actual "business logic" code paths.
Existing code will expect "instanceOf stdClass" to work, and new code
will expect "instanceOf DynamicObject" to work, so an alias (in either
direction) seems the better compromise.
Regards,
--
Rowan Tommins
[IMSoP]
What do people think about adding such an alias? Is this worthwhile?
What if DynamicObject
becomes an interface instead of an alias? In the
future stdClass
could be deprecated and replaced by anonymous classes
using the DynamicObject
interface to prevent manual initialization. I
feel like renaming (this seems to be the end-goal) will not solve some of
the problems that stdClass
brings to php. Hamza Ahmad brought the
interface up in the other thread: https://externals.io/message/115800#115806
.
// core php
interface DynamicObject { /* ... /*}
// in case of removal, legacy support could be kept as
class stdClass implements DynamicObject {
use DynamicObjectTrait; // perhaps also core php?
}
// a library wants a DynamicObject for whatever reason?
final class MyCustomDataObject implements DynamicObject {
public function __get(string $name) : mixed {
// as a developer I now have full control over where
// this data comes from and is stored internally
}
/* ... */
}
To me it feels like making this an interface gives PHP more flexibility in
dealing with this in the future. One intermediate step could be to turn
stdClass
into an interface that extends DynamicObject
. 8.2 could
deprecate manual instantiation of stdClass
, meaning that in 9.0 it could
be removed as class and turned into an interface, reducing the need to
update code relying on the stdClass
types. get_class
would still return
stdClass
in 8.x and thus not break when using a DynamicObject
.