Hi internals,
Another small RFC, to allow $object::class syntax:
https://wiki.php.net/rfc/class_name_literal_on_object
Regards,
Nikita
Another small RFC, to allow $object::class syntax:
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
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
$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
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
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...?
Cheers,
-ralph
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
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
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
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
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
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
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
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.
Hey Marco,
Regarding the inclusion of new keywords with similar behavior of
:class
:Would
Interfaces\FooInterface::trait
orTraits\FooTrait::interface
cause a run time error? If I'm not mistaken::class
can't trigger
error because it can't trigger autoload likeclass_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
Hey Marco,
Regarding the inclusion of new keywords with similar behavior of
:class
:Would
Interfaces\FooInterface::trait
orTraits\FooTrait::interface
cause a run time error? If I'm not mistaken::class
can't trigger
error because it can't trigger autoload likeclass_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
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
Em qui., 9 de jan. de 2020 às 20:57, Mike Schinkel
mike@newclarity.net escreveu:
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
It seems late to mess with ::class
, maybe not for having nameof
.
Em qui., 9 de jan. de 2020 às 20:57, Mike Schinkel
mike@newclarity.net escreveu: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
It seems late to mess with
::class
, maybe not for havingnameof
.
I've never been bothered by Interface::class or Trait::class, frankly. But having to remember slightly different APIs for slightly different cases that I often have to handle together has caused me no end of pain. -1 to needing to think about which type of bracketed reference thingie I am getting a name for.
But having some kind of working way to reference a function that doesn't involve concatenating a string onto a namespace constant would be super nice. Whether it's called ::func or ::nameof I don't much care.
Viz, replace this:
$func = NAMESPACE . '\myfunc`';
With this:
use My\Code\myfunc;
$func = myfunc::nameof;
(Or basically anything that lets me not use strings to reference a function by name.)
To the OP suggestion of $object::class, I don't think I've run into a need for it but since the name is already reserved I don't see a negative; I'm good with it.
--Larry Garfield
But having some kind of working way to reference a function that doesn't involve concatenating a string onto a namespace constant would besuper nice. Whether it's called ::func or ::nameof I don't much care.
Viz, replace this:
$func = NAMESPACE . '\myfunc`';
With this:
use My\Code\myfunc;
$func = myfunc::nameof;
Note that the above example should be:
use function My\Code\myfunc;
$func = myfunc::nameof;
That's important, because we couldn't actually have one operator that
resolved both types, either at compile time or run time. For instance,
this is valid code:
namespace Whatever;
use Bar\Baz as foo;
use function Something\other as foo;
echo foo::class; // "Bar\Baz"
foo(); // runs Something\other(), not Bar\Baz()
A ::func operator would also need to deal with everybody's favourite
name resolution: the dynamic fallback to global functions. In other
words, this...
namespace Foo;
echo strlen::func;
...would need to print "Foo\strlen" if it was defined, and "strlen" if
not. That would then make it odd if a completely undefined function
didn't error, so I don't think it could match the current behaviour of
::class.
Regards,
--
Rowan Tommins (né Collins)
[IMSoP]
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
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
Hi,
[...]
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
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