Is there an RFC/discussion for ::class being a specific type?

php.internals

André Hänsel

4 years ago
It is common (with DI systems for example) and to my knowledge not particularly discouraged to have function parameters that are supposed to accept something like Foo::class, which currently is a string. It seems logical to ask for a special type that can hold class names, so that parameters that can accept a class name can be type hinted more specifically than just (any) "string". Regardless of whether or not such a proposal would be accepted or declined (for complexity reasons maybe) I couldn't even find any such proposal. Has this really never been asked?

Marco Pivetta

4 years ago
Hey André, On Tue, Nov 16, 2021 at 6:22 PM André Hänsel <andre@webkr.de> wrote:
> It is common (with DI systems for example) and to my knowledge not > particularly discouraged to have function parameters that are supposed to > accept something like Foo::class, which currently is a string. > > It seems logical to ask for a special type that can hold class names, so > that parameters that can accept a class name can be type hinted more > specifically than just (any) "string". > > Regardless of whether or not such a proposal would be accepted or declined > (for complexity reasons maybe) I couldn't even find any such proposal. Has > this really never been asked? >
In `vimeo/psalm` and `phpstan/phpstan`, there is a pseudo-type called `class-string`. This `class-string` type is kinda problematic though, because `Foo::class` does **not** autoload `Foo` (by design), and therefore a custom runtime type would be a bit too "optimistic" about its values, as it would not verify anything. Greets, Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/

Kamil Tekiela

4 years ago
Interesting, but what would such a type actually be? How would PHP check the type? What would be the rules? At the moment, ::class is just a preprocessor macro. It is not part of the runtime. It also doesn't mean the name of the class. It just means "textual representation of the value on the left". For example, this is valid: echo "string"::class; You can use ::class on almost anything that the compiler would treat as a string literal. In the end, it will be compiled down to a string. It is handy to think of ::class as ::text or ::name. I don't think we could enforce class type without actually asking PHP to check if class exists. You can use static analysers for this (see class-string), but I doubt it will be possible in PHP.

André Hänsel

4 years ago
Maybe I'm missing something but now that you said it's kind of a macro, I think it would actually be pretty easy to implement, at least when not taking backwards compatibility or performance into account: class ClassName { private $name; function __construct(string $name) { $this->name = $name; } function__toString() { return $name; } } Then make the preprocessor convert Foo::class into `new ClassName(Foo::class)`. Done. Now you can type hint a function like this: class DiContainer { function get(ClassName $klass) { // ... } }

Kamil Tekiela

4 years ago
Ok, but a popular usage is also with functions. For example, strlen::class. What should the compiler use in this case? Replacing a string with an object of a strigable class is not the same. Say I have code like this: function callFoo(callable $func) { echo $func('bar'); } callFoo(strlen::class); This would trigger an error as the ClassName is not an invokable class.

Dusk

4 years ago
On Nov 16, 2021, at 10:56, Kamil Tekiela <tekiela246@gmail.com> wrote:
> Ok, but a popular usage is also with functions. For example, strlen::class. > What should the compiler use in this case?
Popular in what context? I'm not sure this usage is even correct. strlen::class isn't the name of the strlen function; it's the name of the class that you'd be instantiating with "new strlen()", which would be relative to the current namespace. If your code is running in the root namespace, this will happen to resolve to "strlen", but that doesn't mean it's returning a callable.

Kamil Tekiela

4 years ago
Hi Dusk, Perhaps, you misunderstood me. Take a look at the documentation https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class ::class is just a compile time transformation. It will give you the fully qualified name of *something* as a string literal. The only exception to this is when using ::class on objects, as this is a runtime transformation, and when using with keyword static. When ::class is used with a class name, it will give you the name of that class as a string. When used with a name of a function, it will give you the name of that function as a string. When used with a string, it will give you that string as a string. See this example https://3v4l.org/EKnEd The fact that this gives you a callable is just coincidental with all strings being callable in PHP. The reason why it works right now is because a string can represent a name of a class, interface, trait or function. If we were to replace the transformation with something else, like it is proposed here, we would have to ensure that it is as versatile as a plain string. Otherwise we would be losing functionality.

Mel Dafert

4 years ago
On 16 November 2021 23:02:59 CET, Kamil Tekiela <tekiela246@gmail.com> wrote:
>Hi Dusk, > >Perhaps, you misunderstood me. Take a look at the documentation >https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class >::class is just a compile time transformation. It will give you the fully >qualified name of *something* as a string literal. The only exception to >this is when using ::class on objects, as this is a runtime transformation, >and when using with keyword static. > >When ::class is used with a class name, it will give you the name of that >class as a string. When used with a name of a function, it will give you >the name of that function as a string. When used with a string, it will >give you that string as a string. >See this example https://3v4l.org/EKnEd > >The fact that this gives you a callable is just coincidental with all >strings being callable in PHP. The reason why it works right now is >because a string can represent a name of a class, interface, trait or >function. >If we were to replace the transformation with something else, like it is >proposed here, we would have to ensure that it is as versatile as a plain >string. Otherwise we would be losing functionality.
In my opinion, this is extremely unintuitive and surprising. I would not have expected this to work, and would consider this more of an unintended side effect rather than an actual feature of ::class. In my opinion, this is an argument for ::class producing a ClassName type, and for migrating such code that misuses it for functions to the first-class callable syntax (ie. strlen(...)). It might be even possible to issue a deprecation warning whenever a ClassName is coerced to a callable - I am not sure how feasible it would be to implement this, however.

Dusk

4 years ago
On Nov 16, 2021, at 14:02, Kamil Tekiela <tekiela246@gmail.com> wrote:
> When used with a name of a function, it will give you the name of that function as a string.
Again, this is not true. Names of classes and functions are not resolved the same way. Consider: namespace Some\Namespace; use function Other\func; print func::class; This prints "Some\Namespace\func", but calling func() in the same context will refer either to Other\func() or \func(), depending on which is defined -- never to Some\Namespace\func().

Claude Pache

4 years ago
> Le 16 nov. 2021 à 18:34, Kamil Tekiela <tekiela246@gmail.com> a écrit : > > > At the moment, ::class is just a preprocessor macro. It is not part of the > runtime.
This is not true in general. For example `static::class` is not resolvable at compile-time. (In fact, many years ago, when I played with that new feature, I almost filed a bug report saying that `static::class` is not supposed to work, since it is not resolvable at compile-time.) (Additionally, since PHP 8, it is also possible to write `$object::class`, see: https://wiki.php.net/rfc/class_name_literal_on_object <https://wiki.php.net/rfc/class_name_literal_on_object>. But `static::class` worked since 5.5.) —Claude

Guilliam Xavier

4 years ago
> > For example, this is valid: > > echo "string"::class; >
What! Why is it allowed when it gives the same as string::class, and $string::class is an error? https://3v4l.org/hfvXm#v8.1rc3
-- Guilliam Xavier

Guilliam Xavier

4 years ago
> > For example, this is valid: >> >> echo "string"::class; >> > > What! Why is it allowed when it gives the same as string::class, and > $string::class is an error? https://3v4l.org/hfvXm#v8.1rc3 >
Sorry I forgot the namespace... so it really gives the same as "string". https://3v4l.org/rlZFF#v8.1rc3 Still surprising... maybe possibly useful for dynamically generated code? :/
-- Guilliam Xavier

Stephen Reay

4 years ago
> On 17 Nov 2021, at 00:22, André Hänsel <andre@webkr.de> wrote: > > It is common (with DI systems for example) and to my knowledge not > particularly discouraged to have function parameters that are supposed to > accept something like Foo::class, which currently is a string. > > It seems logical to ask for a special type that can hold class names, so > that parameters that can accept a class name can be type hinted more > specifically than just (any) "string". > > Regardless of whether or not such a proposal would be accepted or declined > (for complexity reasons maybe) I couldn't even find any such proposal. Has > this really never been asked? > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Hi André, I’ve wondered about this kind of functionality myself, but IMO to be particularly useful it’d need to support the ability to accept a classname that is a subtype of a parent class or interface. I can’t think of too many places where I’d want to know something is a classname reference, and not also want to know that it's a subclass or implementation of something specific. This typeof functionality could support some very expressive solutions, when combined with union types, and anonymous classes. Cheers Stephen