get_class behavior

php.internals

Lorenzo Fontana

10 years ago
Hello everybody! At the moment the *get_class *function has three possible behaviors - *get_class* is called with the instance of the class and return the name of the class name of the instance; - *get_class* is called without any parameters inside a class and return the containing class name - *get_class* is called without any parameters outside a class, trigger a warning complaining about this and return false At the state of art of PHP the second behavior is not needed anymore because the name of the containing class can be easily obtained via *__CLASS__ or via get_class($this)* or via *get_called_class() *when extending a class in a static method. Moreover *get_class* without any parameters is a possible source of bugs in situations like this: *$result = $repository->find(100);* *echo get_class($result);* If *$result* is *null* the output of *get_class* is the name of the containing class instead of the name of the object I wanted. I would like to a change to the *get_class* behavior in order to support only the first behavior, unfortunately this is a huge BC, so we could start triggering a deprecation warning at first. What do you think? I'm missing something? References: Implementation of *get_class* http://lxr.php.net/xref/PHP_MASTER/Zend/zend_builtin_functions.c#995 Thanks Lorenzo

Dan Ackroyd

10 years ago
On 28 January 2016 at 19:17, Lorenzo Fontana <fontanalorenz@gmail.com> wrote:
> Hello everybody!
Hello Lorenzo,
> If *$result* is *null* the output of *get_class* is the name of the > containing class instead of the name of the object I wanted.
Yes, that is bad. Perhaps a smaller change would give you the desired behaviour though? If we just give a warning/error if any non-object type is passed as the parameter, that would achieve what you want wouldn't it? And it provides a slightly easier 'upgrade' path for people who, for whatever reason, are currently passing null to it.
> the name of the containing class can be easily obtained via *__CLASS__ > or via get_class($this)* or via *get_called_class()
The first two can't be used directly as a callable, and so can't be passed around as a function. And get_called_class() returns the late static binding class. So if the smaller change of just giving a warning/error on any non-object as a parameter gives the desired result, I would recommend doing just that. It might even be possible to do it as a bug fix... cheers Dan ## Refactoring if non-objects are no longer allowed. Current code: echo get_class($result); Would need to be changed to: if ($result === null) { echo get_class(); } else { echo get_class($result); } ## get_called_class is late static binding. class foo { static function bar() { //return $fn(null); echo get_class()."\n"; echo get_called_class()."\n"; } } class Quux extends foo { } Quux::bar(); // Output is: // foo // Quux ## Callables can passed around class foo { public static function bar(callable $fn) { return $fn(); } } echo foo::bar('get_class');

Lorenzo Fontana

10 years ago
2016-01-28 23:35 GMT+01:00 Dan Ackroyd <danack@basereality.com>:
> On 28 January 2016 at 19:17, Lorenzo Fontana <fontanalorenz@gmail.com> > wrote: > > Hello everybody! > > Hello Lorenzo, > > > > If *$result* is *null* the output of *get_class* is the name of the > > containing class instead of the name of the object I wanted. > > Yes, that is bad. Perhaps a smaller change would give you the desired > behaviour though? > > If we just give a warning/error if any non-object type is passed as > the parameter, that would achieve what you want wouldn't it? And it > provides a slightly easier 'upgrade' path for people who, for whatever > reason, are currently passing null to it. > > > the name of the containing class can be easily obtained via *__CLASS__ > > or via get_class($this)* or via *get_called_class() > > The first two can't be used directly as a callable, and so can't be > passed around as a function. And get_called_class() returns the late > static binding class. > > So if the smaller change of just giving a warning/error on any > non-object as a parameter gives the desired result, I would recommend > doing just that. It might even be possible to do it as a bug fix... > > cheers > Dan > > ## Refactoring if non-objects are no longer allowed. > > Current code: > echo get_class($result); > > Would need to be changed to: > > if ($result === null) { > echo get_class(); > } > else { > echo get_class($result); > } > > ## get_called_class is late static binding. > > class foo { > static function bar() { > //return $fn(null); > echo get_class()."\n"; > echo get_called_class()."\n"; > } > } > > class Quux extends foo { } > > Quux::bar(); > > // Output is: > // foo > // Quux > > > ## Callables can passed around > > class foo { > public static function bar(callable $fn) { > return $fn(); > } > } > > echo foo::bar('get_class'); >
Yes, that is bad. Perhaps a smaller change would give you the desired
> behaviour though? > If we just give a warning/error if any non-object type is passed as > the parameter, that would achieve what you want wouldn't it? And it > provides a slightly easier 'upgrade' path for people who, for whatever > reason, are currently passing null to it.
Yes is what I would like to do, start saying people that they should change that by triggering a deprecation notice so they can refactor their code. The first two can't be used directly as a callable, and so can't be
> passed around as a function. And get_called_class() returns the late > static binding class.
I don't see any way to get the declaring class passing a callable as you pointed out in the following code if not using *get_class* without parameters. Any idea?
> > class foo { > public static function bar(callable $fn) { > return $fn(); > } > } > echo foo::bar('get_class');
Thanks, Lorenzo

Lorenzo Fontana

10 years ago
2016-01-29 0:22 GMT+01:00 Lorenzo Fontana <fontanalorenz@gmail.com>:
> 2016-01-28 23:35 GMT+01:00 Dan Ackroyd <danack@basereality.com>: > >> On 28 January 2016 at 19:17, Lorenzo Fontana <fontanalorenz@gmail.com> >> wrote: >> > Hello everybody! >> >> Hello Lorenzo, >> >> >> > If *$result* is *null* the output of *get_class* is the name of the >> > containing class instead of the name of the object I wanted. >> >> Yes, that is bad. Perhaps a smaller change would give you the desired >> behaviour though? >> >> If we just give a warning/error if any non-object type is passed as >> the parameter, that would achieve what you want wouldn't it? And it >> provides a slightly easier 'upgrade' path for people who, for whatever >> reason, are currently passing null to it. >> >> > the name of the containing class can be easily obtained via *__CLASS__ >> > or via get_class($this)* or via *get_called_class() >> >> The first two can't be used directly as a callable, and so can't be >> passed around as a function. And get_called_class() returns the late >> static binding class. >> >> So if the smaller change of just giving a warning/error on any >> non-object as a parameter gives the desired result, I would recommend >> doing just that. It might even be possible to do it as a bug fix... >> >> cheers >> Dan >> >> ## Refactoring if non-objects are no longer allowed. >> >> Current code: >> echo get_class($result); >> >> Would need to be changed to: >> >> if ($result === null) { >> echo get_class(); >> } >> else { >> echo get_class($result); >> } >> >> ## get_called_class is late static binding. >> >> class foo { >> static function bar() { >> //return $fn(null); >> echo get_class()."\n"; >> echo get_called_class()."\n"; >> } >> } >> >> class Quux extends foo { } >> >> Quux::bar(); >> >> // Output is: >> // foo >> // Quux >> >> >> ## Callables can passed around >> >> class foo { >> public static function bar(callable $fn) { >> return $fn(); >> } >> } >> >> echo foo::bar('get_class'); >> > > > Yes, that is bad. Perhaps a smaller change would give you the desired >> behaviour though? >> If we just give a warning/error if any non-object type is passed as >> the parameter, that would achieve what you want wouldn't it? And it >> provides a slightly easier 'upgrade' path for people who, for whatever >> reason, are currently passing null to it. > > > > > Yes is what I would like to do, start saying people that they should > change that by triggering a deprecation notice so they can refactor their > code. > > > The first two can't be used directly as a callable, and so can't be >> passed around as a function. And get_called_class() returns the late >> static binding class. > > > I don't see any way to get the declaring class passing a callable as you > pointed out in the following code if not using *get_class* without > parameters. Any idea? > >> >> class foo { >> public static function bar(callable $fn) { >> return $fn(); >> } >> } >> echo foo::bar('get_class'); > > > > Thanks, > Lorenzo > > >
Another way to obtain the declaring class in static methods but it's not a callable is the use of *self::class* *class foo { static function bar() { echo self::class."\n"; echo get_called_class()."\n"; }}class Quux extends foo { }Quux::bar();// Output is:// foo// Quux*

Dan Ackroyd

10 years ago
On 28 January 2016 at 23:22, Lorenzo Fontana <fontanalorenz@gmail.com> wrote:
> > Yes is what I would like to do, start saying people that they should change > that by triggering a deprecation notice so they can refactor their code.
I don't think we need a deprecation notice. The behaviour when the function is passed a null parameter is bogus, and could just be changed to emit an E_WARNING for the PHP 7.1 release. cheers Dan

Rowan Collins

10 years ago
Dan Ackroyd wrote on 28/01/2016 22:35:
> ## Refactoring if non-objects are no longer allowed. > > Current code: > echo get_class($result); > > Would need to be changed to: > > if ($result === null) { > echo get_class(); > } > else { > echo get_class($result); > }
You appear to be suggesting that "get_class()" should behave differently than "get_class(null)". Normally a function doesn't distinguish between "called with no parameters" and "called with default parameters". I don't know if internal parameter handling (ZPP) works like userland parameters in this respect, but if not impossible this would at least be rather surprising. It also doesn't help with your stated point of using get_class as a callable, since you'd have to know what function it was in order to call it in the right way (although exactly what the use case for that would be, I've no idea). Regards, Rowan Collins
-- [IMSoP]

Dan Ackroyd

10 years ago
On 29 January 2016 at 10:46, Rowan Collins <rowan.collins@gmail.com> wrote:
> > You appear to be suggesting that "get_class()" should behave differently > than "get_class(null)". > I don't know if internal parameter handling (ZPP) works like userland > parameters
Regardless of how internals works, it's a thing in userland PHP. function foo($bar = null) { echo "number of args is: ".func_num_args(). "\n"; } foo(); foo(null); // Output is: // number of args is: 0 // number of args is: 1 It's a useful thing to do when you want to distinguish between null being passed, and the default value being used. Just like in this case. The same is possible for internal functions. cheers Dan

Rowan Collins

10 years ago
Dan Ackroyd wrote on 29/01/2016 13:56:
> On 29 January 2016 at 10:46, Rowan Collins <rowan.collins@gmail.com> wrote: >> You appear to be suggesting that "get_class()" should behave differently >> than "get_class(null)". >> I don't know if internal parameter handling (ZPP) works like userland >> parameters > Regardless of how internals works, it's a thing in userland PHP. > > function foo($bar = null) > { > echo "number of args is: ".func_num_args(). "\n"; > } > > foo(); > foo(null); > > // Output is: > // number of args is: 0 > // number of args is: 1 > > It's a useful thing to do when you want to distinguish between null > being passed, and the default value being used. Just like in this > case. The same is possible for internal functions.
OK, so it's possible, but it's not something I've seen done very often, and in my opinion it would be a really confusing thing to do. "Ah, an optional parameter, what does it default to?" "Nothing" "You mean null?" "No, null has a separate meaning" "Separate from what?" "Nothing" etc... It's worryingly reminiscent of the endless debates about isset() and distinguishing an non-existent variable from one which has been explicitly set to null - you're inventing an extra kind of "very null". Presumably you'd get to this point by saying "if $bar is an int, do this; if $bar is null, do this; oh, I need a third case, let's do some magic with func_num_args()"; so what happens when you need a fourth case? Even worse, in this case, you're doing the opposite of what many functions do - rather than saying "this parameter used to be optional, but now you have to be specific even if you pass null", you're saying "this parameter is still optional, but if you pass its default value in you'll get a notice/warning". I understand the intention, but I think this approach would just lead to confusion. Regards,
-- Rowan Collins [IMSoP]