[RFC] Allow ::class on objects

php.internals

Nikita Popov

6 years ago
Hi internals, Another small RFC, to allow $object::class syntax: https://wiki.php.net/rfc/class_name_literal_on_object Regards, Nikita

Ralph Schindler

6 years ago
> Another small RFC, to allow $object::class syntax: > > https://wiki.php.net/rfc/class_name_literal_on_object
Hi Nikita, Glad to see enhancements to ::class. Open questions, given: class Foo {} $foo = new Foo; echo $foo::class; // Foo $fooClass = Foo::class; echo $foo::class; // TypeError b/c string? $fooAnon = new class extends Foo {}; echo $fooAnon::class; // TypeError or class@anonymous? There's prob a few more edge cases, but this is a start. -ralph

Nikita Popov

6 years ago
On Thu, Jan 9, 2020 at 3:28 PM Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > Another small RFC, to allow $object::class syntax: > > > > https://wiki.php.net/rfc/class_name_literal_on_object > > Hi Nikita, > > Glad to see enhancements to ::class. Open questions, given: > > > class Foo {} > > $foo = new Foo; > echo $foo::class; // Foo >
> $fooClass = Foo::class; > echo $foo::class; // TypeError b/c string? >
Yes, at least per the current proposal. Whether to allow this is listed as an open question.
> $fooAnon = new class extends Foo {}; > echo $fooAnon::class; // TypeError or class@anonymous?
The same as get_class($fooAnon), which will be of the form "class@anonymous\0SOME_PAYLOAD_HERE". While the part after class@anonymous is cut of when printing in most places, anonymous classes still do have a unique class name. Nikita

Nicolas Grekas

6 years ago
> > $fooClass = Foo::class; > > echo $foo::class; // TypeError b/c string? > > Yes, at least per the current proposal. Whether to allow this is listed as > an open question. >
I missed that part! If it could return `string`, that'd be super useful! We have tons of lines of code that look like: is_object($foo) ? get_class($foo) : gettype($foo) Getting rid of them would be very sweet ! Nicolas

Nikita Popov

6 years ago
On Thu, Jan 9, 2020 at 9:49 PM Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > > $fooClass = Foo::class; >> > echo $foo::class; // TypeError b/c string? >> >> Yes, at least per the current proposal. Whether to allow this is listed as >> an open question. >> > > I missed that part! > > If it could return `string`, that'd be super useful! > We have tons of lines of code that look like: is_object($foo) ? > get_class($foo) : gettype($foo) > Getting rid of them would be very sweet ! > > Nicolas >
That's not how it would work, if it were allowed. $string::class would just return $string, to be consistent with usual behavior of "::CONST_NAME", which allows objects and class names on the left hand side. I think it would be pretty nice to have a function for the use case you mention though, as this is indeed very common for error messages. Nikita

Ralph Schindler

6 years ago
> just return $string, to be consistent with usual behavior of > "::CONST_NAME", which allows objects and class names on the left hand side.
Having given it more thought, it seems like like $string::class should be more aligned with get_class($string), which at current throws a PHP Warning with the message: "PHP Warning: get_class() expects parameter 1 to be object, string given in ..." From the RFC:
> I'm not sure whether we should do that, as I can't imagine a context > in which this would be useful, and the fact that the class name is > not validated or loaded might be more unexpected here than usual.
Yes, this would be odd and not useful: $hi = 'hello world'; echo $hi::class // output: hello world Besides that though, not sure what you mean by:
> Open Question: Additionally, it would be possible to also allow $object > to be string, in which case the string would be returned verbatim. This > would be consistent with the $className::CONST_NAME syntax.
As undefined class constants are fatal errors...? https://3v4l.org/hGW82 Cheers, -ralph

Girgias

6 years ago
On Tue, 14 Jan 2020 at 19:05, Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > > just return $string, to be consistent with usual behavior of > > "::CONST_NAME", which allows objects and class names on the left hand > side. > > Having given it more thought, it seems like like $string::class should > be more aligned with get_class($string), which at current throws a PHP > Warning with the message: > > "PHP Warning: get_class() expects parameter 1 to be object, string > given in ..." >
This is going to be a TypeError in PHP 8 following the consistent type error RFC [1] therefore the current behaviour to throw a TypeError is inline which get_class() George P. Banyard [1] https://wiki.php.net/rfc/consistent_type_errors

Stas Malyshev

6 years ago
Hi!
> If it could return `string`, that'd be super useful!
Would it? There's no class "string". So for all purposes other than printing out, that would be a pretty big footgun.
> We have tons of lines of code that look like: is_object($foo) ? > get_class($foo) : gettype($foo) > Getting rid of them would be very sweet !
It could be useful to have generic function for describing objects, but that's not what ::class should be doing. It should not return things which aren't classes.
-- Stas Malyshev smalyshev@gmail.com

Mike Schinkel

6 years ago
> On Jan 9, 2020, at 5:27 AM, Nikita Popov <nikita.ppv@gmail.com> wrote: > > Hi internals, > > Another small RFC, to allow $object::class syntax:
+1 Along those lines, is there any consideration of adding ::interface and ::trait, where applicable? -Mike

Ralph Schindler

6 years ago
> Along those lines, is there any consideration of adding ::interface and ::trait, where applicable?
How would this be applicable? In the context of variables, an object can be only of a single class type, whereas it can be an *instanceof* multiple interfaces (or an extension of other types) or be *composed of* multiple traits (in full or partial if there are trait conflicts.) How would you get the right semantics out of $object::interface, or $object::trait, and/or do you have an example of what you're expecting? -ralph

Mike Schinkel

6 years ago
> On Jan 9, 2020, at 3:29 PM, Ralph Schindler <ralph@ralphschindler.com> wrote:How would you get the right semantics out of $object::interface, or $object::trait, and/or do you have an example of what you're expecting?
Sorry, I was only thinking about using it on Interface and Trait names, not on objects. I have a lot of code that looks like this: use Interfaces; if ( ! implements_interface( $object, Interfaces\MyInterface::class, $trigger_error = true )) { return; } And it feels wrong. I would love to be able to use ::interface, i.e.: use Interfaces; if ( implements_interface( $object, Interfaces\MyInterface::interface, $trigger_error = true )) { return; } -Mike

Kalle Sommer Nielsen

6 years ago
Moi Den tor. 9. jan. 2020 kl. 22.41 skrev Mike Schinkel <mike@newclarity.net>:
> > > On Jan 9, 2020, at 3:29 PM, Ralph Schindler <ralph@ralphschindler.com> wrote:How would you get the right semantics out of $object::interface, or $object::trait, and/or do you have an example of what you're expecting? > > Sorry, I was only thinking about using it on Interface and Trait names, not on objects.
Traits are compiler assisted code copy/paste and not contracts (unlike interfaces), so there is no gain in having ::trait.
> I have a lot of code that looks like this: > > use Interfaces; > if ( ! implements_interface( $object, Interfaces\MyInterface::class, $trigger_error = true )) { > return; > } > > And it feels wrong. I would love to be able to use ::interface, i.e.: > > use Interfaces; > if ( implements_interface( $object, Interfaces\MyInterface::interface, $trigger_error = true )) { > return; > }
If your $object variable is an actual instance, you can use the instanceof operator, it treats the right operand as a first class citizen and allows you to skip writing ::class: use Interfaces; if(!$object instanceof Interfaces\MyInterface) { // Notice the ! is right associative and instanceof is non associative, hence the lack of parantheses } use Interfaces; if($object instanceof Interfaces\MyInterface) { }
-- regards, Kalle Sommer Nielsen kalle@php.net

Mike Schinkel

6 years ago
> Den tor. 9. jan. 2020 kl. 22.41 skrev Mike Schinkel <mike@newclarity.net>: > > Traits are compiler assisted code copy/paste and not contracts (unlike > interfaces), so there is no gain in having ::trait.
It can already be referring to using ::class so it makes little sense to disallow ::trait unless there is a different reason not to add another ::keyword. Traits are symbols, so it is not unreasonable that there would be a way to access it symbolically so that the reference can be type checked. One of my use-cases for referring to traits is to provide helpful hints in error messages, i.e.: printf( '%s does not implement %s. You can easily implement that interface by using %s', get_class( $this ), Interfaces\Foo::interface, Traits\Foo::trait, );
>> use Interfaces; >> if ( implements_interface( $object, Interfaces\MyInterface::interface, $trigger_error = true )) { >> return; >> } > If your $object variable is an actual instance, you can use the > instanceof operator, it treats the right operand as a first class > citizen and allows you to skip writing ::class:
But the instanceof operator does not fulfill my requirements. I manage error triggering during development and logging during production within the function. Thus I can just use the implements_interface() function to handle errors automatically when I am testing an assertion vs. using it just for branching. You'll note I explicitly included a third parameter `$trigger_error = true` in hopes people would notice, realize what I was doing and not think that I needed to be educated on the existence of instanceof. -Mike

Marcio Almada

6 years ago
About the proposal itself of allowing `($expression)::class`, I'd be in favor.
> > > Den tor. 9. jan. 2020 kl. 22.41 skrev Mike Schinkel <mike@newclarity.net>: > > > > Traits are compiler assisted code copy/paste and not contracts (unlike > > interfaces), so there is no gain in having ::trait. > > It can already be referring to using ::class so it makes little sense to disallow ::trait unless there is a different reason not to add another ::keyword. > > Traits are symbols, so it is not unreasonable that there would be a way to access it symbolically so that the reference can be type checked. > > One of my use-cases for referring to traits is to provide helpful hints in error messages, i.e.: >
Regarding the inclusion of new keywords with similar behavior of `:class`: Would `Interfaces\FooInterface::trait` or `Traits\FooTrait::interface` cause a run time error? If I'm not mistaken `::class` can't trigger error because it can't trigger autoload like `class_exists()` calls does. Currently `::class` can be used to resolve any name like `trim::class`. IMMO it would be weird to have `::class` with the current no autoload / error free behavior and then `::trait`, `::function` and `::interface` triggering autoload and emitting some error level. And also it would be weird to have `::trait`, `::function` and `::interface` with the same loose behavior as `::class`, in that case, it seems less surprising to have just `::class` everywhere instead.

Mike Schinkel

6 years ago
Hey Marco,
> On Jan 9, 2020, at 5:04 PM, Marcio Almada <marcio.web2@gmail.com> wrote: > > Regarding the inclusion of new keywords with similar behavior of `:class`: > > Would `Interfaces\FooInterface::trait` or `Traits\FooTrait::interface` > cause a run time error? If I'm not mistaken `::class` can't trigger > error because it can't trigger autoload like `class_exists()` calls > does. > > Currently `::class` can be used to resolve any name like > `trim::class`. IMMO it would be weird to have `::class` with the > current no autoload / error free behavior and then `::trait`, > `::function` and `::interface` triggering autoload and emitting some > error level.
You are absolutely correct. I am so used to PhpStorm highlighting issues that I sometimes assume that PHP will throw an error/warning for the same. So if ::interface and ::trait were added, then (tools like) PhpStorm could have enough information to flag them if developers misspell them or they are otherwise unknown.
> And also it would be weird to have `::trait`, `::function` and > `::interface` with the same loose behavior as `::class`
Why weird? I can only assume Foo::trait vs. Bar::interface vs. Baz::class would not be hard to grok, and that it would would provide more valuable information to a reader than Foo::class vs. Bar:: class vs. Baz::class.
> in that case, it seems less surprising to have just `::class` > everywhere instead.
How so? I was very surprised when I first realized that I could do MyInterface::class, but I would not have been surprised by MyInterface::interface.
> `::function`
That said you bring up one I did not but would also like ::function as well as `::method` and even ::var or ::variable and ::const or ::constant. -Mike

Marcio Almada

6 years ago
> > Hey Marco, > > > On Jan 9, 2020, at 5:04 PM, Marcio Almada <marcio.web2@gmail.com> wrote: > > > > Regarding the inclusion of new keywords with similar behavior of `:class`: > > > > Would `Interfaces\FooInterface::trait` or `Traits\FooTrait::interface` > > cause a run time error? If I'm not mistaken `::class` can't trigger > > error because it can't trigger autoload like `class_exists()` calls > > does. > > > > Currently `::class` can be used to resolve any name like > > `trim::class`. IMMO it would be weird to have `::class` with the > > current no autoload / error free behavior and then `::trait`, > > `::function` and `::interface` triggering autoload and emitting some > > error level. > > You are absolutely correct. I am so used to PhpStorm highlighting issues that I sometimes assume that PHP will throw an error/warning for the same. > > So if ::interface and ::trait were added, then (tools like) PhpStorm could have enough information to flag them if developers misspell them or they are otherwise unknown. > > > And also it would be weird to have `::trait`, `::function` and > > `::interface` with the same loose behavior as `::class` > > Why weird? >
Because we would be expanding a construct that already looks inappropriate from a purely semantic POV with aliases that also would allow inappropriate usage `some_function::interface`. I'd rather have a generally unsatisfying construct than a set of "denormalized" constructs with equal potential for human inaccuracy. Perhaps the problem is that `::class` was not exactly a good language design decision in the first place or maybe it made more sense in the past and as the language evolved it started to appear to be named poorly. A more _general_ construct like a `nameof` operator, as we have in C#, could have been a brighter idea. See: - https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof - https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#nameof-expressions
> I can only assume Foo::trait vs. Bar::interface vs. Baz::class would not be hard to grok, and that it would would provide more valuable information to a reader than Foo::class vs. Bar:: class vs. Baz::class. > > > in that case, it seems less surprising to have just `::class` > > everywhere instead. > > How so? I was very surprised when I first realized that I could do MyInterface::class, but I would not have been surprised by MyInterface::interface. >
Others could say they get more astounded with `SomeTrait::function` being equally valid. It's much easier to explain that `::class` is general and works everywhere (even though I don't think it's good).
> > `::function` > > That said you bring up one I did not but would also like ::function as well as `::method` and even ::var or ::variable and ::const or ::constant. >
Hahaha You're welcome :P Marcio

Mike Schinkel

6 years ago
> On Jan 9, 2020, at 6:53 PM, Marcio Almada <marcio.web2@gmail.com> wrote: > > Because we would be expanding a construct that already looks > inappropriate from a purely > semantic POV with aliases that also would allow inappropriate usage > `some_function::interface`. > I'd rather have a generally unsatisfying construct than a set of > "denormalized" constructs with > equal potential for human inaccuracy. > > Perhaps the problem is that `::class` was not exactly a good language > design decision in the first place or maybe > it made more sense in the past and as the language evolved it started > to appear to be named poorly. > > A more _general_ construct like a `nameof` operator, as we have in C#, > could have been a brighter idea. See:
Okay, I get your perspective on this now. So let us add ::nameof and deprecate ::class, then? And apply it for all symbols? :-) -Mike

Kalle Sommer Nielsen

6 years ago
Den tor. 9. jan. 2020 kl. 23.30 skrev Mike Schinkel <mike@newclarity.net>:
> > > Den tor. 9. jan. 2020 kl. 22.41 skrev Mike Schinkel <mike@newclarity.net>: > > > > Traits are compiler assisted code copy/paste and not contracts (unlike > > interfaces), so there is no gain in having ::trait. > > It can already be referring to using ::class so it makes little sense to disallow ::trait unless there is a different reason not to add another ::keyword.
It is not about adding a new keyword; the keyword "trait" already exists and "::trait" is a combination of an operator and an already existing keyword (since we are educating each other). I cannot see any benefits to adding the ::trait syntax (nor the ::interface) one besides some OCD issue that you are using ::class on an instance or name of something that technically is not a class, like in your below example, even less when ::class already is working.
> Traits are symbols, so it is not unreasonable that there would be a way to access it symbolically so that the reference can be type checked.
That is where you are wrong, traits are not contracts, so therefore they are not types. You cannot check that a class reuses a trait or not when it is constructed because it is compiler assisted copy/paste. They are a useless symbol to refer to in the context of ::class. I can understand you wish to make a helpful error message to a developer who fails to write a class that is loadable by your runtime, but in your example you are still refering to the trait as it was a type, which it technically isn't.
> > One of my use-cases for referring to traits is to provide helpful hints in error messages, i.e.: > > printf( '%s does not implement %s. You can easily implement that interface by using %s', > get_class( $this ), > Interfaces\Foo::interface, > Traits\Foo::trait, > ); > > But the instanceof operator does not fulfill my requirements. I manage error triggering during development and logging during production within the function. Thus I can just use the implements_interface() function to handle errors automatically when I am testing an assertion vs. using it just for branching. > > You'll note I explicitly included a third parameter `$trigger_error = true` in hopes people would notice, realize what I was doing and not think that I needed to be educated on the existence of instanceof.
That is fine if the instanceof operator does not do it for you, but you could have left that last part of the comment out of the mail, after all, it was just a tip.
-- regards, Kalle Sommer Nielsen kalle@php.net

Mike Schinkel

6 years ago
> On Jan 9, 2020, at 5:30 PM, Kalle Sommer Nielsen <kalle@php.net> wrote: > > I cannot see any benefits to adding the ::trait syntax (nor the ::interface) one > besides some OCD issue that you are using ::class on an instance or name > of something that technically is not a class, like in your below > example, even less when ::class already is working.
The benefit is _clarity_ for when reading code. Is wanting clarity considered OCD?
>> Traits are symbols, so it is not unreasonable that there would be a way to access it symbolically so that the reference can be type checked. > > That is where you are wrong, traits are not contracts, so therefore > they are not types.
That is a distinction without a difference. And I did not say I wanted to check it for a contract. I simply wanted to reference it.
> They are a useless symbol to refer to in the context of ::class.
I said I wanted to refer to it when composing error message, as one example. That is not useless. Why is it so important to push back on this? What harm does having it cause when clarity is the benefit?
> but in your example you are still referring to the trait as it was a type
How am I referring to the trait as a type? I simply referenced it in an error message.
> That is fine if the instanceof operator does not do it for you, but > you could have left that last part of the comment out of the mail, > after all, it was just a tip.
I am frustrated by people constantly telling me how they think I should code when I ask for features, that people assume because they cannot envision it that a feature is problematic, and I was annoyed you assumed that as someone on this list I would need that tip. But you are right. I apologize. -Mike

Guilliam Xavier

6 years ago
Hi, On Thu, Jan 9, 2020 at 10:02 PM Kalle Sommer Nielsen <kalle@php.net> wrote:
> > [...] > > use Interfaces; > if(!$object instanceof Interfaces\MyInterface) > { > // Notice the ! is right associative and instanceof is non > associative, hence the lack of parantheses > }
Sorry for off-topic but that comment is incorrect: the fact that `!$x instanceof Foo` is evaluated as `!($x instanceof Foo)` (which I find more readable with explicit parentheses, by the way) is not due to the *associativity* of the operators but to their relative *precedence*. For instance, `===` is non-associative too but `!$x === 42` is evaluated as `(!$x) === 42` (not as `!($x === 42)`). According to the docs, associativity only matters for operators of equal precedence, e.g. `4 - 3 - 2` is evaluated as `(4 - 3) - 2`, and `4 ** 3 ** 2` is evaluated as `4 ** (3 ** 2)`.
-- Guilliam Xavier

Nikita Popov

6 years ago
On Thu, Jan 9, 2020 at 11:27 AM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > Another small RFC, to allow $object::class syntax: > > https://wiki.php.net/rfc/class_name_literal_on_object > > Regards, > Nikita >
Heads up: I plan to start voting on this tomorrow. I've resolved the open question re strings in favor of not supported strings, in line with discussion consensus. Nikita