Hello Internals,
It is with much trepidation and excitement that I'd like to announce
the nameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.
Essentially, it allows using nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).
If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NL
Wow! Yess please. My mail yesterday crossed yours, I guess (thanks Claude
for answering)
asking for almost the same:
myClass::$normalProperty::name
So I would like to add my thoughts to your proposal.
I prefer a magic constant like ::name
instead of a function call as it
can be used in a wider scope; for example as a parameter in an Attribute.
My arguments from yesterday:
'Directly' accessing properties and methods of the class as a parameter
in an Attribute is not really possible.
You can do that for a class with: \path\to\MyClass::class, but for
properties or methods you have to use strings, without recognition in
editors.It would be so nice if more items in php could be accessed with a
magic constant as you can do with ::class.
With magic constant an editor could recognize it and it limits typos
etc. and it provides the developer with a list of the properties/methods in
scope.
Some examples from my mail yesterday for using in attributes:
abstract class SaveFactoryAbstract {
use ObjectBuilderTrait;
#[ResetValue]
protected bool $isSaved = false;
#[InitObject] // tells the builder to initialize this property with the
latest specs provided by (other) attributes
#[ResetObject]
protected ?SaveInterface $oSave = null;
public function __construct(){
$this->buildMe(); // provided by the trait
}
}
With a magic constant you could do:
#[UseClass(static::$oSave::name, ImageSave::class)] // using static for
normal property?
#[ResetValue($this->isSaved::name, default: true)] // or using $this ??
class ImageSaveFactory extends SaveFactoryAbstract {
// negative side-effect: empty classes.....
}
From my mail yesterday:
This could also extend possibilities with/for variable variables....
Like:
$this->myProp::name
myClass::$staticProp::name
A potential difficulty is: how to access normal properties/methods....
Maybe just as if they are static?
Like parent::normalMethod() is working....
A normal property or method cannot have the same name as its static partner
(sadly) so internally I hope it is not a problem.
outside scope:
myClass::$staticProperty::name
myClass::$normalProperty::name
myClass::normalMethod::name
inside scope:
static::$normalProperty::name
self::$normalProperty::name // I do not know if 'self::' adds anything for
this purpose, except for private properties/methods maybe
inside class/object:
$this->normalProperty::name
$this->normalMethod::name
$this::$staticProperty::name
Greetz, Lydia
P.S. I hope the spacing in de code-example is working now.
Op za 13 mei 2023 om 09:27 schreef Robert Landers <landers.robert@gmail.com
:
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NL--
To unsubscribe, visit: https://www.php.net/unsub.php
I prefer a magic constant like
::name
instead of a function call as it
can be used in a wider scope; for example as a parameter in an Attribute.
Don't confuse the syntax with the implementation: "::class" isn't actually a constant, and the proposed "nameof()" isn't actually a function; both are just syntaxes understood by the compiler to generate strings according to certain rules. The RFC explicitly mentions use in attributes:
The nameof() function is not necessarily a function in the traditional sense and can be used anywhere a string literal can be used (such as to define static constants, array keys, and attributes).
I'm lukewarm on the RFC myself - I can't think of many places I'd use it, and some of the use cases feel like they would ideally not involve strings at all. But I can't actually think of a good reason not to include it.
The one part of the RFC that surprised me was this:
When getting the name of constants and functions, the name will NOT be the full name, but the lexical name. This means that if the name is use'd, it will be that name. However, if the full name is used in the nameof(), the full name is returned.
No reason is given why it works this way, and expanding the namespaces on a name is one of the most useful features of "::class". Importantly, it means that the string created can be passed to other contexts, and still refer to the same thing. For instance:
use function Acme\bar as foo;
...
#[SomeAttribute(callback: nameof(foo(...))]
...
The attribute constructor, defined in a different file, can't do anything useful with the string 'foo'; it needs to know the qualified name of the function the user referenced, 'Acme\bar'.
Similarly, I'm not wholly clear why nameof(MyClass::method(...)) should evaluate to "method" rather than "MyClass::method" or indeed "Full\NamespaceOf\MyClass::method". Some more details on what exactly it evaluates to for each type, and why, would be helpful.
Regards,
--
Rowan Tommins
[IMSoP]
The one part of the RFC that surprised me was this:
When getting the name of constants and functions, the name will NOT be the full name, but the lexical name. This means that if the name is use'd, it will be that name. However, if the full name is used in the nameof(), the full name is returned.
No reason is given why it works this way, and expanding the namespaces on a name is one of the most useful features of "::class". Importantly, it means that the string created can be passed to other contexts, and still refer to the same thing. For instance:
I'll update the RFC for the reasoning later tonight, but it's actually
a pretty simple one. There are other languages that have something
similar (C# and Scala, from personal experience) and usually, they
provide an unqualified name. This can be annoying, for exactly the
reason you describe: when you want to pass the value to another
context. Most of the time, you want the unqualified name, but then
there are times you want the qualified name, and getting it can be a
complex process. By allowing developers to provide a fully qualified
name, if that is what they want, developers have more control over
their experience.
This is especially important when using traits and aliases:
trait A {
public function example() {}
}
trait B {
public function example() {}
}
class C {
use A, B {
A::example insteadof B;
B::example as exampleB;
}
}
$a = new C();
echo nameof($a->exampleB(...)); // outputs: "exampleB"
The actual 'qualified name' is "example" or maybe "B::example" but
that isn't very helpful if you want to call $a->B::example instead of
$a->A::example later on. Or consider if you have an aliased const:
namespace A {
const A = 1;
}
namespace B {
use const A\A as TEST;
echo nameof(TEST);
}
In this case, if you want to use nameof(TEST) in an error message or
something else, it would be surprising to get \A\A instead of TEST.
So, for your example, we can instead call nameof(\Acme\bar(...))
instead of the aliased name when passing to another context:
use function Acme\bar as foo;
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...
I hope this helps!
Similarly, I'm not wholly clear why nameof(MyClass::method(...)) should evaluate to "method" rather than "MyClass::method" or indeed "Full\NamespaceOf\MyClass::method".
Usually, you want the name of the 'last' thing". For example, if you
want the name of a property called "myProperty", using
nameof($this->myProperty), you probably just want "myProperty" and not
"{some_internal_instance_id}->myProperty" that you have to parse to
really use. Instead, we can compse ::class and/or nameof() to achieve
the same thing if we want to.
Most of the time, you want the unqualified name
...
This is especially important when using traits and aliases:
I don't think trait renaming is the same thing as aliasing a qualified name. In a sense, it's actually the opposite: with a trait, the compiler is inserting the code of the method under the new name, and the name from the trait can no longer be used to access it; for namespace imports, the compiler is substituting the fully-qualified name, and the local alias can no longer be used to access it.
The "real" name of a method copied in from a trait is whatever it's called in the target class, but the "real" name of a function aliased with "use function ... as ..." is undoubtedly its fully-qualified name.
In this case, if you want to use nameof(TEST) in an error message or
something else, it would be surprising to get \A\A instead of TEST.
Would it? As I said before, my intuition was completely the opposite, that one of the advantages of writing nameof(TEST) rather than just 'TEST' is that it would resolve namespace imports the same way ::class does.
Can you give an example of such an error message that would want to expose the local alias?
So, for your example, we can instead call nameof(\Acme\bar(...))
instead of the aliased name when passing to another context:use function Acme\bar as foo;
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...I hope this helps!
Not really, I'm afraid. If I have to write out the fully-qualified name, why would I bother with nameof() rather than just using a string?
The more I read in this thread, the less I understand what the point of nameof() actually is, and how it would be used.
Regards,
--
Rowan Tommins
[IMSoP]
Most of the time, you want the unqualified name
...
This is especially important when using traits and aliases:I don't think trait renaming is the same thing as aliasing a qualified name. In a sense, it's actually the opposite: with a trait, the compiler is inserting the code of the method under the new name, and the name from the trait can no longer be used to access it; for namespace imports, the compiler is substituting the fully-qualified name, and the local alias can no longer be used to access it.
As someone who has had limited interaction with the engine code itself
but has been using PHP, daily, for nearly 15 years, I'm not sure this
technical distinction makes sense in the context of programming in
PHP. When using traits, I don't think of it as 'inserting code', but
rather I think of it as 'using code' -- you do use use TraitName
after all and it is very similar to the top-level using statement.
In this case, if you want to use nameof(TEST) in an error message or
something else, it would be surprising to get \A\A instead of TEST.Would it? As I said before, my intuition was completely the opposite, that one of the advantages of writing nameof(TEST) rather than just 'TEST' is that it would resolve namespace imports the same way ::class does.
Can you give an example of such an error message that would want to expose the local alias?
Which error message helps in resolving the error and would be written
by someone writing the code?
use function \Path\To\Function\Serializer\to_json as json_serializer;
// a bunch of code using json_serializer()
throw new Exception('json_serializer returned invalid json');
// or
throw new Exception('\Path\To\Function\Serializer\to_json returned
invalid json');
The former appears more natural and helps to trace the issue.
Otherwise, you end up in the file, trying to dig through usings to
figure out where usages of \Path\To\Function\Serializer\to_json are in
the file because, at first glance, it appears to not be there.
This would also be weird in the case of constants/functions in the
global space, because fully qualified names begin with a "" which
feels unnatural when reading things meant for humans (such as error
messages).
So, for your example, we can instead call nameof(\Acme\bar(...))
instead of the aliased name when passing to another context:use function Acme\bar as foo;
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...I hope this helps!
Not really, I'm afraid. If I have to write out the fully-qualified name, why would I bother with nameof() rather than just using a string?
If you write it in a string you really will, actually, have to write
out the fully qualified name because an IDE won't know you're
referencing a language construct and be able to autocomplete it for
you. If you rename the function or remove it, you won't be able to use
static analysis to locate these strings, you'll have to manually
search-and-replace the codebase, which opens up space for human error.
nameof() gives you a fighting chance of actually finding ALL the
usages in one go.
Personally, I've found that refactoring the names of things in PHP is
one of the most dangerous coding activities one can do in PHP. They
may be in a string, a database, on the wire via serialize(),
or any
number of places. Making this a safer activity is one of my personal
goals.
The more I read in this thread, the less I understand what the point of nameof() actually is, and how it would be used.
IMHO, this comment isn't constructive or helpful, how can I help you
understand? I'm more than happy to answer any and all questions, but
comments like this are pretty demoralizing when there are only a few
people discussing the feature in a community I'm relatively new to --
are my answers not clear enough, would a call help, or should I
reference other language's implementations and rationales? I don't
know the unwritten rules here, and being helpful and constructive can
move the conversation forward instead of reaching impasses.
Personally, I've been in war and dealt with far worse than this, but
someone who hasn't been through what I've been through may not be as
"thick-skinned" and just walk away. Just something to keep in mind for
future interactions.
As someone who has had limited interaction with the engine code itself
but has been using PHP, daily, for nearly 15 years, I'm not sure this
technical distinction makes sense in the context of programming in
PHP. When using traits, I don't think of it as 'inserting code', but
rather I think of it as 'using code' -- you do useuse TraitName
after all and it is very similar to the top-level using statement.
I don't think it's a technical distinction at all, it's part of the definition of traits in PHP, which are often described as "compiler assisted copy and paste". The methods from the trait are added to the definition of the class during compilation, and after that it is almost impossible to distinguish them from methods written directly in the class definition.
Conversely, namespace imports are entirely local to the current file (or even a namespace{} block), and it's almost impossible for running code to see anything other than the expanded name.
That doesn't mean that there aren't good reasons to have nameof() handle both things in the way proposed, I just think those reasons are probably different for the two cases.
Can you give an example of such an error message that would want to expose the local alias?
Which error message helps in resolving the error and would be written
by someone writing the code?
To clear up any doubt, there was no sarcasm or aggression intended, I was genuinely requesting an example, which you have provided, so thank you.
use function \Path\To\Function\Serializer\to_json as json_serializer;
// a bunch of code using json_serializer()
throw new Exception('json_serializer returned invalid json');
// or
throw new Exception('\Path\To\Function\Serializer\to_json returned
invalid json');
I can see your reasoning now, but I can think of arguments both ways round: if you're trying to find out why the function returned invalid JSON, you need to know where the source code for that function is, so need its fully qualified name.
My gut feel is still that it's more often useful to have the expansion done for you, but maybe others have different perspectives.
This would also be weird in the case of constants/functions in the
global space, because fully qualified names begin with a "" which
feels unnatural when reading things meant for humans (such as error
messages).
This seems to be a common sentiment indeed - people prefer long lists of "use function" to \ prefixes. It's not something I personally find unnatural, any more than "/images/logo.png" or "/etc/hosts", but I'm willing to concede I may be in a minority there.
Not really, I'm afraid. If I have to write out the fully-qualified name, why would I bother with nameof() rather than just using a string?
If you write it in a string you really will, actually, have to write
out the fully qualified name because an IDE won't know you're
referencing a language construct and be able to autocomplete it for
you. If you rename the function or remove it, you won't be able to use
static analysis to locate these strings, you'll have to manually
search-and-replace the codebase, which opens up space for human error.
This makes some sense, although static analysis tools and IDEs already do a lot with pseudo-types in docblocks to understand the expected values of strings.
The biggest weakness of a generic "nameof" is that it still doesn't hint what kind of thing you want to mention.
IMHO, this comment isn't constructive or helpful, how can I help you
understand? I'm more than happy to answer any and all questions, but
comments like this are pretty demoralizing when there are only a few
people discussing the feature in a community I'm relatively new to
I'm genuinely sorry you felt this way, and it was absolutely not my intention to criticise you. If anything, it was meant to be an admission of my own failings, that I'd come into the discussion with the wrong preconceptions, because the use cases which immediately came into my head seemed to have contrasting requirements from the ones that you intended.
To put things into a more productive context, then, I think a useful improvement to the RFC would be some examples of how you would use it with the different supported types, showing why the proposed semantics are useful for those scenarios.
On a different note, you've talked about how errors would be handled for nonexistent constructs, but given PHP's dynamic nature, there's also a question of when they would need to exist. For instance, assuming we go with Warnings, if I use nameof(Foo::bar), and class Foo is defined in a different file, there's a few things that could happen:
- the resolution happens while compiling a single file, in which case it will always give a Warning, because any symbol outside the file is unknown
- the resolution happens at runtime, and causes class Foo to be autoloaded
- the resolution happens at runtime but doesn't trigger autoloading, so whether it gives a Warning or not depends on whether other code happens to have loaded the definition of Foo
- probably other options that I haven't considered
With the "no error handling" version, this becomes moot, because it's just a string manipulation exercise. That's how ::class currently works, apart from a few specific cases like static::class and $someInstance::class
Regards,
--
Rowan Tommins
[IMSoP]
Conversely, namespace imports are entirely local to the current file (or even a namespace{} block), and it's almost impossible for running code to see anything other than the expanded name.
That doesn't mean that there aren't good reasons to have nameof() handle both things in the way proposed, I just think those reasons are probably different for the two cases.
That's a fair point. Maybe it is worth putting it up for a secondary
vote? I do have a strong preference, but I'd rather have full names
than no names, and doesn't change the RFC that much. If I'm
understanding correctly, it would only affect first-class callables
and constants.
Can you give an example of such an error message that would want to expose the local alias?
Which error message helps in resolving the error and would be written
by someone writing the code?To clear up any doubt, there was no sarcasm or aggression intended, I was genuinely requesting an example, which you have provided, so thank you.
FWIW, I didn't see it as a sarcastic question, but I'm a very
sarcastic person IRL... so maybe I did and I just ignored it. ¯_
()_/¯ Either way, it was a good question.
This seems to be a common sentiment indeed - people prefer long lists of "use function" to \ prefixes. It's not something I personally find unnatural, any more than "/images/logo.png" or "/etc/hosts", but I'm willing to concede I may be in a minority there.
I've seen hundreds of lines of nothing but use
... it's terrifying
once you end up with a smattering of aliases thrown in, randomly, for
good measure. I don't think you're in the minority for people who've
been using PHP a long time -- or bitten by those files. As someone
who's had to wade through those codebases, aliases matter a lot and
I'd like to see them respected. I'm also realizing, as this discussion
goes on, that there is precedent for the expansion via ::class, so
maybe that is the right way to go after all? 🤔
The biggest weakness of a generic "nameof" is that it still doesn't hint what kind of thing you want to mention.
The 'kind' of the thing doesn't really matter, we just want the name
of the thing. Basically, the string before (...)
; the string after
->
,::
, $
, or in the case of constants, the string itself.
IMHO, this comment isn't constructive or helpful, how can I help you
understand? I'm more than happy to answer any and all questions, but
comments like this are pretty demoralizing when there are only a few
people discussing the feature in a community I'm relatively new toI'm genuinely sorry you felt this way, and it was absolutely not my intention to criticise you. If anything, it was meant to be an admission of my own failings, that I'd come into the discussion with the wrong preconceptions, because the use cases which immediately came into my head seemed to have contrasting requirements from the ones that you intended.
Thanks for explaining your intentions and no hard feelings from me. It
can feel like an 'island of one' putting an RFC forward for the first
time and a bit daunting when receiving negative feedback.
Maybe there should be a 'buddy system' for first-time submissions that
reach the discussion phase? A more experienced contributor could
volunteer to help guide the person through the process, answer weird
questions (like when is a secondary vote required or useful? I got
some feedback that it was a bad idea to include one, but why? I'm sure
there's some background there, but I'm in the dark with nobody to
reach out to and ask for history lessons), give informal feedback, and
someone to reach out to when you get stuck.
To put things into a more productive context, then, I think a useful improvement to the RFC would be some examples of how you would use it with the different supported types, showing why the proposed semantics are useful for those scenarios.
I can do that for sure.
On a different note, you've talked about how errors would be handled for nonexistent constructs, but given PHP's dynamic nature, there's also a question of when they would need to exist. For instance, assuming we go with Warnings, if I use nameof(Foo::bar), and class Foo is defined in a different file, there's a few things that could happen:
- the resolution happens while compiling a single file, in which case it will always give a Warning, because any symbol outside the file is unknown
- the resolution happens at runtime, and causes class Foo to be autoloaded
- the resolution happens at runtime but doesn't trigger autoloading, so whether it gives a Warning or not depends on whether other code happens to have loaded the definition of Foo
- probably other options that I haven't considered
With the "no error handling" version, this becomes moot, because it's just a string manipulation exercise. That's how ::class currently works, apart from a few specific cases like static::class and $someInstance::class
I actually updated the RFC late last night with that information after
some feedback from Dan. ;)
(I hope it is ok that I take parts from several mails)
Op za 13 mei 2023 om 12:37 schreef Rowan Tommins rowan.collins@gmail.com:
I prefer a magic constant like
::name
instead of a function call as it
can be used in a wider scope; for example as a parameter in an Attribute.Don't confuse the syntax with the implementation: "::class" isn't actually a constant, and the proposed "nameof()" isn't actually a function; both are just syntaxes understood by the compiler to generate strings according to certain rules. The RFC explicitly mentions use in attributes:
Just got that from the php manual.... 😜 : as it is listed under the
'magic constants'
(https://www.php.net/manual/en/language.constants.magic.php)
But I understand your point.
The one part of the RFC that surprised me was this:
When getting the name of constants and functions, the name will NOT be the full name, but the lexical name. This means that if the name is use'd, it will be that name. However, if the full name is used in the nameof(), the full name is returned.
No reason is given why it works this way, and expanding the namespaces on a name is one of the most useful features of "::class". Importantly, it means that the string created can be passed to other contexts, and still refer to the same thing.
Op zo 14 mei 2023 om 14:55 schreef Rowan Tommins rowan.collins@gmail.com:
use function \Path\To\Function\Serializer\to_json as json_serializer;
// a bunch of code using json_serializer()
throw new Exception('json_serializer returned invalid json');
// or
throw new Exception('\Path\To\Function\Serializer\to_json returned
invalid json');I can see your reasoning now, but I can think of arguments both ways round: if you're trying to find out why the function returned invalid JSON, you need to know where the source code for that function is, so need its fully qualified name.
My gut feel is still that it's more often useful to have the expansion done for you, but maybe others have different perspectives.
Op zo 14 mei 2023 om 17:45 schreef Robert Landers landers.robert@gmail.com:
Conversely, namespace imports are entirely local to the current file (or even a namespace{} block), and it's almost impossible for running code to see anything other than the expanded name.
That doesn't mean that there aren't good reasons to have nameof() handle both things in the way proposed, I just think those reasons are probably different for the two cases.
That's a fair point. Maybe it is worth putting it up for a secondary
vote? I do have a strong preference, but I'd rather have full names
than no names, and doesn't change the RFC that much. If I'm
understanding correctly, it would only affect first-class callables
and constants.
I totally agree with Rowan. In many (most?) cases you need the fully
qualified name.
Even or especially for error handling! Of course mostly the error is
somewhere else. But you want to know the starting point. An
unqualified name gets you nowhere. Although backtrace can help you
out.
And this is also consistent with how ::class or get_class()
work....
it gives fully qualified names.
On the other hand you could argue that 'nameof()' in itself means:
'just looking for the direct/unqualified name of something' and is an
addition to ::class and get_class()
....
Then we still need something to get the fully qualified name/path of a
variable or php-item.
For a class we have: ::class, get_class()
and class_exists()
.
And inside these function you can use:
get_class(MyClass::class);
For a method we only have: method_exists and it needs 2 parameters,
for the methodname: only string is possible 😭
Would be nice to have, and consistent to have:
- MyClass::method::method
- method_exists($object->method::method)
- get_method(MyClass::method::method)
And the same for properties:
- MyClass::property::property
- method_exists($object-> property::property)
- get_method(MyClass:: property::property)
The downside is that we get many new 'magic constants'...
What about:
-
for unqualified names:
-- nameof(any-php-item)
-- any_php_item::nameof -
for fully qualified names:
-- pathof(any-php-item) or fullnameof()
-- any_php_item::pathof or ::fullnameof
To put things into a more productive context, then, I think a useful improvement to the RFC would be some examples of how you would use it with the different supported types, showing why the proposed semantics are useful for those scenarios.
I can do that for sure.
Aside for error handling, I think it would give attributes a boost as
they handle configuration and metadata, without having a proper way to
address variables and other php items, except classes.
#[MyAttribute(property: MyClass::myProperty::fullnameof)]
Which IMHO is much better then:
#[MyAttribute(property: callback(nameof(MyClass::myProperty))]
and if it only gives the unqualified name:
#[MyAttribute(property: MyClass::class . callback(nameof(MyClass::myProperty))]
Which also shows that it is not consistent..... with the useful
working of ::class
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...
This callback to nameof()..... is really terrible 😣😣
Is this not why ::class was invented, while get_class()
does the same?!
Op zo 14 mei 2023 om 00:42 schreef Robert Landers landers.robert@gmail.com:
I think the downside of using ::name would be: 'what if I already have
a const called "name"?'So, we'd have to come up with a different name. Largely, as far as the
compiler would be concerned, we've both got the same idea.
Good point.
I hope my suggestions above do help.
And that you consider adding them to your RFC 🙏
instead of
$class = new ReflectionClass(ClassName::class);
$property = $class->getProperty('property');
$type = $property->getType();
var_dump($type);
It is worse.... you need an extra if with ->hasProperty....
Something to think about...
Greetz, Lydia
I totally agree with Rowan. In many (most?) cases you need the fully
qualified name.
Even or especially for error handling! Of course mostly the error is
somewhere else. But you want to know the starting point. An
unqualified name gets you nowhere. Although backtrace can help you
out.And this is also consistent with how ::class or
get_class()
work....
it gives fully qualified names.
On the other hand you could argue that 'nameof()' in itself means:
'just looking for the direct/unqualified name of something' and is an
addition to ::class andget_class()
....
This is exactly what I was thinking when I wrote it out, however, you
can pass the full name to get the full name (an escape hatch of
sorts).
Then we still need something to get the fully qualified name/path of a
variable or php-item.
For a class we have: ::class,get_class()
andclass_exists()
.
And inside these function you can use:
get_class(MyClass::class);
Variables don't get stored in a namespace or they exist only in the
local scope (they can't have a 'qualified' name, in my experience):
namespace A { $b = 'c'; }
echo $b; // outputs: 'c'
For callables, you can already get the fully qualified name:
echo (new ReflectionFunction(strstr(...)))->getName();
You, however, cannot get the name of a constant, except through
get_defined_constants()
and comparing values (searching in the
namespace you care about) and doesn't help you if it isn't defined.
There's further evidence that an unqualified name is the right way to go:
use const \NoExist\TEST;
use \NoExist\Channel;
echo TEST;
echo Channel::class;
the output is:
Undefined constant "TEST" in ...
Channel
note that it does NOT say "\MyNamespace\TEST" is undefined nor does it
output "\NoExist\Channel", so if the user wants to write:
use function \AppPlugins\Config;
if(!function_exists(nameof(Config(...))))
throw new Exception(nameof(Config(...)) . ' does not exist, please
define it.');
It's going to output the unqualified name anyway, simply due to how
PHP resolves names. Using the "escape hatch", however, we can "force"
PHP to do what we expect:
if(!function_exists(nameof(\AppPlugins\Config(...)))
throw new Exception(nameof(Config(...)) . ' does not exist, please
define it.');
I'll update the RFC to reflect this.
The downside is that we get many new 'magic constants'...
There are no magic constants in this RFC.
Robert Landers
Software Engineer
Utrecht NL
use const \NoExist\TEST;
use \NoExist\Channel;
echo TEST;
echo Channel::class;the output is:
Undefined constant "TEST" in ...
Channel
I'm not sure how you got that; according to https://3v4l.org/gltKN only PHP 5.6 has a message with the unqualified name:
Notice: Use of undefined constant TEST - assumed 'TEST' in /in/gltKN on line 5
Before that, there was no "use const", and in every version after that, it's a fatal error with the fully qualified name:
Fatal error: Uncaught Error: Undefined constant "NoExist\TEST" in /in/gltKN:5
Take the constant part away so that the next line runs, and https://3v4l.org/q0XQO shows the fully qualified name right back up PHP 5.5, because expanding the name is actually the only thing that ::class does in that context.
Regards,
--
Rowan Tommins
[IMSoP]
use const \NoExist\TEST;
use \NoExist\Channel;
echo TEST;
echo Channel::class;the output is:
Undefined constant "TEST" in ...
ChannelI'm not sure how you got that; according to https://3v4l.org/gltKN only PHP 5.6 has a message with the unqualified name:
Notice: Use of undefined constant TEST - assumed 'TEST' in /in/gltKN on line 5
Before that, there was no "use const", and in every version after that, it's a fatal error with the fully qualified name:
Fatal error: Uncaught Error: Undefined constant "NoExist\TEST" in /in/gltKN:5
Take the constant part away so that the next line runs, and https://3v4l.org/q0XQO shows the fully qualified name right back up PHP 5.5, because expanding the name is actually the only thing that ::class does in that context.
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
php --version
PHP 8.2.6 (cli) (built: May 12 2023 06:24:00) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.6, Copyright (c) Zend Technologies
with Zend OPcache v8.2.6, Copyright (c), by Zend Technologies
with Xdebug v3.2.0, Copyright (c) 2002-2022, by Derick Rethans
php -a
Interactive shell
php > use \NoExists\Channel;
php > echo Channel::class;
Channel
Turns out, it's a CLI specific thing, when running in a file, it does
indeed output the full name. I stand corrected; I tend to use the CLI
for testing out stuff.
php -a
Interactive shellphp > use \NoExists\Channel;
php > echo Channel::class;
ChannelTurns out, it's a CLI specific thing
Ah, probably each line in the interactive shell is considered a separate compilation context, like a separate file, so the use statement is no longer in effect on the next line.
3v4l.org is great for verifying things like this, because you can see at a glance if there's been a change in behaviour in previous versions, or indeed versions newer than you have installed. If you've got an up to date PhpStorm, you can actually use it directly from a scratch file. (Huge shout-out to Sjön Hortensius for making and maintaining such a useful tool!)
Regards,
--
Rowan Tommins
[IMSoP]
3v4l.org is great for verifying things like this, because you can see at a glance if there's been a change in behaviour in previous versions, or indeed versions newer than you have installed. If you've got an up to date PhpStorm, you can actually use it directly from a scratch file. (Huge shout-out to Sjön Hortensius for making and maintaining such a useful tool!)
I've never used a scratch file in PhpStorm until just now and it is
quite magical, thanks for the tip.
Robert Landers
Software Engineer
Utrecht NL
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...This callback to nameof()..... is really terrible 😣😣
Just to be clear, the "callback:" in that example is existing syntax for named parameters, not part of the nameof() syntax. A positional parameter would just look like this:
#[SomeAttribute(nameof(\Acme\bar(...))]
That's the same number of characters as this:
#[SomeAttribute(\Acme\bar(...)::nameof]
The main difference is the the function-ish syntax makes it a bit clearer what you're asking for the name of, because the whole thing is in parentheses. There might even be cases where the function-ish syntax needs additional parentheses anyway, like are needed in (new Something)->doSomething()
Changing "nameof" to "function", "method", etc depending on type doesn't really add anything, since the output is always a string anyway, not some type-specific thing.
Regards,
--
Rowan Tommins
[IMSoP]
Op ma 15 mei 2023 om 20:06 schreef Rowan Tommins rowan.collins@gmail.com:
...
#[SomeAttribute(callback: nameof(\Acme\bar(...))]
...This callback to nameof()..... is really terrible 😣😣
Just to be clear, the "callback:" in that example is existing syntax for
named parameters, not part of the nameof() syntax. A positional parameter
would just look like this:#[SomeAttribute(nameof(\Acme\bar(...))]
That's the same number of characters as this:
#[SomeAttribute(\Acme\bar(...)::nameof]
oops 🤭😳 tx!
So because nameof() is not a real function, it works as parameter in
attributes.... nice!
The main difference is the the function-ish syntax makes it a bit clearer
what you're asking for the name of, because the whole thing is in
parentheses. There might even be cases where the function-ish syntax needs
additional parentheses anyway, like are needed in (new
Something)->doSomething()
Sure.
I just think we need both nameof() and ::nameof
Just like we have get_class()
and ::class.....
Changing "nameof" to "function", "method", etc depending on type doesn't
really add anything, since the output is always a string anyway, not some
type-specific thing.
True.... I only suggested that because '::class' exists and not something
like: ::nameof or ::pathof.
And thought that maybe the choice for ::class was deliberately
Op ma 15 mei 2023 om 19:45 schreef Robert Landers <landers.robert@gmail.com
:
There are no magic constants in this RFC.
May I ask why you do not add a magic constant as part of your RFC?
If I would make an RFC for it, it would be almost a duplicate of yours.....
And I cannot contribute to the codebase, as I do not know C or in whatever
language php is written :-(
But I could help with documenting and writing for the RFC.
Quote from RFC:
"When getting the name of constants and functions, the name will NOT be the
full name, but the lexical name. This means that if the name is
use'd, it will be that name. However, if the full name is used in
the nameof(), the full name is returned. It's probably easiest to show
this with an example:
<...>
echo nameof(MY_CONST); // MY_CONST
echo nameof(\Name\MY_CONST); // \Name\MY_CONST
"
This looks confusing to me. As it is best practice to import with use and
without using fully qualified names inside your code.
Maybe consider a second bool parameter to full name or not.
Or have a second 'function' like: fullnameof()
Looking again at your RFC.... I just realized.... it is about variables,
properties, functions, methods etc. not about classes... 🤔 (ploink)
In that case you are right that you mostly want the unqualified name. Most
of them do not even have a different fully qualified name.
Only items that are namespace aware have that.
But local variables and items inside a class, they have just one name. Or
am I mistaken here?
So maybe it does not help to talk about fully qualified or not.
Greetz, Lydia
Wow! Yess please. My mail yesterday crossed yours, I guess (thanks Claude
for answering)
asking for almost the same:myClass::$normalProperty::name
So I would like to add my thoughts to your proposal.
I prefer a magic constant like
::name
instead of a function call as it
can be used in a wider scope; for example as a parameter in an Attribute.My arguments from yesterday:
'Directly' accessing properties and methods of the class as a parameter
in an Attribute is not really possible.
You can do that for a class with: \path\to\MyClass::class, but for
properties or methods you have to use strings, without recognition in
editors.It would be so nice if more items in php could be accessed with a
magic constant as you can do with ::class.
With magic constant an editor could recognize it and it limits typos
etc. and it provides the developer with a list of the properties/methods in
scope.Some examples from my mail yesterday for using in attributes:
abstract class SaveFactoryAbstract {
use ObjectBuilderTrait;
#[ResetValue]
protected bool $isSaved = false;
#[InitObject] // tells the builder to initialize this property with the
latest specs provided by (other) attributes#[ResetObject]
protected ?SaveInterface $oSave = null;
public function __construct(){
$this->buildMe(); // provided by the trait
}
}
With a magic constant you could do:
#[UseClass(static::$oSave::name, ImageSave::class)] // using static for
normal property?#[ResetValue($this->isSaved::name, default: true)] // or using $this ??
class ImageSaveFactory extends SaveFactoryAbstract {// negative side-effect: empty classes.....
}
From my mail yesterday:
This could also extend possibilities with/for variable variables....Like:
$this->myProp::name
myClass::$staticProp::nameA potential difficulty is: how to access normal properties/methods....
Maybe just as if they are static?
Like parent::normalMethod() is working....
A normal property or method cannot have the same name as its static partner
(sadly) so internally I hope it is not a problem.outside scope:
myClass::$staticProperty::name
myClass::$normalProperty::name
myClass::normalMethod::nameinside scope:
static::$normalProperty::name
self::$normalProperty::name // I do not know if 'self::' adds anything for
this purpose, except for private properties/methods maybeinside class/object:
$this->normalProperty::name
$this->normalMethod::name
$this::$staticProperty::nameGreetz, Lydia
P.S. I hope the spacing in de code-example is working now.Op za 13 mei 2023 om 09:27 schreef Robert Landers <landers.robert@gmail.com
:
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NL--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Lydia,
Please remember to bottom-post responses. But, to respond:
I think the downside of using ::name would be: 'what if I already have
a const called "name"?'
So, we'd have to come up with a different name. Largely, as far as the
compiler would be concerned, we've both got the same idea.
A potential difficulty is: how to access normal properties/methods....
Maybe just as if they are static?
Like parent::normalMethod() is working....
A normal property or method cannot have the same name as its static partner
(sadly) so internally I hope it is not a problem.
I find this particular point an interesting side discussion, as it was
something that bothered me for a while. Ultimately, I ended up with
two solutions:
- Do no error checking at all with nameof.
This lets the community come up with something that makes sense.
Perhaps someday in the future the community will settle on something
that works well and an RFC will be put forward to add proper error
checking. For example, any of these could work, and all resolve to the
string "normalMethod":
- nameof(ClassName::normalMethod)
- nameof(ClassName::normalMethod(...))
- nameof($_->normalMethod)
- nameof(ClassName->normalMethod(...)) <-- I kinda like this one
- nameof(normalMethod)
- Emit a warning
This is the one I suspect will be chosen (and I'm starting to like
more and more, after writing out those examples). In this
implementation, we'd emit a warning if you used a name that doesn't
exist. However, PHP has a well-known silence operator (@) that could
be used to silence the warning if you wanted to do something weird
(like one of the examples above). It would still allow an RFC for
referencing instanced properties/methods at a later date, if there is
demand for it. Until then, there is the silence operator.
If there was an RFC for referencing instanced properties or methods,
it could be interesting. Right now, the only way to reference
instanced properties is via reflection, and having this capability
would open the door for having a generic 'declared_type()' (not to be
confused with gettype()
that returns the actual type of a variable and
not the declared type) that could instantly return the reflected type.
I can think of a number of applications for something like that... and
all the code I could delete...
It would be magical to write something like:
var_dump(declared_type(ClassName->property));
instead of
$class = new ReflectionClass(ClassName::class);
$property = $class->getProperty('property');
$type = $property->getType();
var_dump($type);
Something to think about...
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof).
Can you provide more details on what the error conditions are? I can
see 'non-existent static variable' and 'non-existent variable', are
there others?
An exception model was considered, however, the author believes that
it would break the concept of “using nameof wherever a string could
be used” and wouldn't be practical for the engine to handle all of
those cases.
Can you put some detail in there ....I don't understand the problem,
or what you mean by it not being practical to 'handle all of those
cases' .
each one has its own merits but ultimately will
be left up as a secondary vote:
One of the reasons the RFC process has served PHP pretty well is that
it forces people to think through the details.
I really don't think putting a few options up and hoping that people
choose the best one is a good way to design a language; it allows
skipping over thinking the details through.
And yeah....this is one of the reason doing RFCs is annoying. People
are often persnickety over details.
cheers
Dan
Ack
Can you provide more details on what the error conditions are? I can
see 'non-existent static variable' and 'non-existent variable', are
there others?
Absolutely! I'll add it to the RFC later tonight, but the gist is that
it would be exactly the same as if you were to call any function with
something that doesn't exist.
An exception model was considered, however, the author believes that
it would break the concept of “using nameof wherever a string could
be used” and wouldn't be practical for the engine to handle all of
those cases.Can you put some detail in there ....I don't understand the problem,
or what you mean by it not being practical to 'handle all of those
cases' .
I can do that, I agree it probably isn't as clear as it could be.
One issue that was identified is that if this can be used anywhere a
string/constant can be used, there may be edge cases where there is no
way to handle an exception there because a try-catch isn't an
expression. For example, in attributes, I'm not 100% sure the engine
is set up to handle an exception being thrown while defining an
attribute, nor how you would catch such an exception in a graceful
way. I could be completely wrong, in which case I can add back that
option.
each one has its own merits but ultimately will
be left up as a secondary vote:One of the reasons the RFC process has served PHP pretty well is that
it forces people to think through the details.I really don't think putting a few options up and hoping that people
choose the best one is a good way to design a language; it allows
skipping over thinking the details through.And yeah....this is one of the reason doing RFCs is annoying. People
are often persnickety over details.
That is fair :)
After some discussions with colleagues, I realized how errors are
output and handled might be a contentious topic. I decided to make it
a secondary vote -- and hopefully -- the discussions we're having now
might inform voters on the best way to vote for how errors are
handled. I don't have a personal preference (but apparently some
people do), and I'd be happy with any of the solutions.
Can you provide more details on what the error conditions are? I can
see 'non-existent static variable' and 'non-existent variable', are
there others?Absolutely! I'll add it to the RFC later tonight, but the gist is that
it would be exactly the same as if you were to call any function with
something that doesn't exist.
After enumerating the warnings/errors that would be expressed, I
realized that some of these would halt execution, which is probably
undesirable. I've adjusted the RFC to simply emit a warning and
further defined when that warning would be emitted.
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NL
Hello Robert
Thank you for doing the proposal. I can see some places where I might use this.
I have an additional question that I don't think has been asked yet. About the interaction of nameof with "variable variables":
What should be returned by nameof($$variable)
, and similarly nameof($$$variable)
, etc?
Defining the semantics for this, and giving examples in the RFC text would be great :)
Good luck with your RFC.
Cheers
Niels
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NLHello Robert
Thank you for doing the proposal. I can see some places where I might use this.
I have an additional question that I don't think has been asked yet. About the interaction of nameof with "variable variables":
What should be returned bynameof($$variable)
, and similarlynameof($$$variable)
, etc?
Defining the semantics for this, and giving examples in the RFC text would be great :)Good luck with your RFC.
Cheers
Niels
That is a good question.
It should be a compile error due to ambiguity. I'll update the RFC to
include that.
$variable would already be the name of the variable being referenced
by $$variable, so if you want the name of $variable, just get the name
of $variable, and if you want the name of $$variable, just get the
value of $variable.
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NL
Some concrete use cases that I know I run into, and would thus be what I'd hope an RFC like this would resolve:
Router::addRoute('\my\space\my_action_function`);
Right now that has to be a string with a full namespace. You cannot use a FCC here, because you want to use this data to compile the router somehow. So in this case we want the full namespace. (Ignore function autoloading for now.)
Router::addRoute(MyClass::actionMethod);
Various frameworks have different custom syntaxes for this case. It's also not clear if this refers to a static method, or a "instantiate this out of the container first and then call this method" approach. In these cases, we would want the full class name, and the method name on its own, as separate strings.
In my FP library, I have code like this:
function prop(string $prop): \Closure
{
return static fn (object $o): mixed => $o->$prop;
}
function method(string $method, ...$args): \Closure
{
return static fn (object $o): mixed => $o->$method(...$args);
}
Which you can then use in a pipe, like so:
pipe($someObject, method('foo'), prop('bar'));
Or, more realistically, you'd use method() and prop() in a map or filter call within the pipe.
In this case, you want just the method/property name on its own, without a namespace, because it will be used in the context of an object to be named later.
How would nameof() handle each of these cases?
--Larry Garfield
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NLSome concrete use cases that I know I run into, and would thus be what I'd hope an RFC like this would resolve:
Router::addRoute('\my\space\my_action_function`);
Right now that has to be a string with a full namespace. You cannot use a FCC here, because you want to use this data to compile the router somehow. So in this case we want the full namespace. (Ignore function autoloading for now.)
Router::addRoute(MyClass::actionMethod);
Various frameworks have different custom syntaxes for this case. It's also not clear if this refers to a static method, or a "instantiate this out of the container first and then call this method" approach. In these cases, we would want the full class name, and the method name on its own, as separate strings.
In my FP library, I have code like this:
function prop(string $prop): \Closure
{
return static fn (object $o): mixed => $o->$prop;
}function method(string $method, ...$args): \Closure
{
return static fn (object $o): mixed => $o->$method(...$args);
}Which you can then use in a pipe, like so:
pipe($someObject, method('foo'), prop('bar'));
Or, more realistically, you'd use method() and prop() in a map or filter call within the pipe.
In this case, you want just the method/property name on its own, without a namespace, because it will be used in the context of an object to be named later.
How would nameof() handle each of these cases?
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Hey Larry,
I did not ignore you. I somehow missed your question while researching
something after reading the first half of your email last week -- I,
apparently, never made it to the second half until today. I'll get to
that in a sec.
pipe($someObject, method('foo'), prop('bar'));
Or, more realistically, you'd use method() and prop() in a map or filter call within the pipe.
In this case, you want just the method/property name on its own, without a namespace, because it will be used in the context of an object to be named later.
So, for example, you could replace your pipe with:
pipe($someObject, method(nameof($someObject->foo(...))),
prop(nameof($someObject->bar)))
It looks a bit wordy, but PHP differentiates between properties and
methods by using a parenthesis/bracket (hence if you want to call a
property that is actually a closure, you have to write ($this->prop)()
otherwise it will look for a method called prop and fail to find it).
At runtime, you would receive a warning on this line if $someObject is
missing a method called foo or a property called bar (if the
error-detecting version is chosen). This would result likely result in
an actual error in your method() and prop() functions. However, it
should make debugging from logs a bit easier since you'll (hopefully)
notice the warning.
Now, why I didn't answer you and only made it half way through your email...
Much of the earlier discussion was about functions/consts being
fully-qualified names. I was mostly against it, because I was offering
'both' qualified and unqualified. However, your initial examples
really hit home for me and I did a bit of spelunking in various
popular PHP codebases for class::name (since that is really the only
similar thing) and for stringified names of things. I'm now convinced
that FQL's should be returned for functions, and constants (but not
methods and properties).
Thus, your example:
Router::addRoute('\my\space\my_action_function`);
Router::addRoute(MyClass::actionMethod);
could be rewritten as
use function \my\space\my_action_function;
Router::addRoute(nameof(my_action_function));
Router::addRoute(nameof(MyClass::actionMethod(...)));
I'll have to think about how to word this in RFC, but I'll do so as
soon as I figure it out.
Cheers,
Robert Landers
use function \my\space\my_action_function;
Router::addRoute(nameof(my_action_function));
Router::addRoute(nameof(MyClass::actionMethod(...)));
My mistake, this should be:
use function \my\space\my_action_function;
Router::addRoute(nameof(my_action_function));
Router::addRoute(MyClass::name . '::' . nameof(MyClass::actionMethod(...))); // or something since the class name is not returned with nameof().
Hello Internals,
It is with much trepidation and excitement that I'd like to announce
thenameof
RFC (https://wiki.php.net/rfc/nameof). It has changed
quite a bit in the last couple of days, so if you haven't seen the
latest draft, please check it out.Essentially, it allows using
nameof()
anywhere a string can be used,
even in static contexts. From a developer's perspective, it is a
string and from the engine's perspective, it is also mostly a string
(depending on how deep we want to go on error checking -- see the
RFC).If anything is unclear or if I missed something, please let me know.
Robert Landers
Software Engineer
Utrecht NLSome concrete use cases that I know I run into, and would thus be what I'd hope an RFC like this would resolve:
Router::addRoute('\my\space\my_action_function`);
Right now that has to be a string with a full namespace. You cannot use a FCC here, because you want to use this data to compile the router somehow. So in this case we want the full namespace. (Ignore function autoloading for now.)
Router::addRoute(MyClass::actionMethod);
Various frameworks have different custom syntaxes for this case. It's also not clear if this refers to a static method, or a "instantiate this out of the container first and then call this method" approach. In these cases, we would want the full class name, and the method name on its own, as separate strings.
In my FP library, I have code like this:
function prop(string $prop): \Closure
{
return static fn (object $o): mixed => $o->$prop;
}function method(string $method, ...$args): \Closure
{
return static fn (object $o): mixed => $o->$method(...$args);
}Which you can then use in a pipe, like so:
pipe($someObject, method('foo'), prop('bar'));
Or, more realistically, you'd use method() and prop() in a map or filter call within the pipe.
In this case, you want just the method/property name on its own, without a namespace, because it will be used in the context of an object to be named later.
How would nameof() handle each of these cases?
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Hey Larry,
I did not ignore you. I somehow missed your question while researching
something after reading the first half of your email last week -- I,
apparently, never made it to the second half until today. I'll get to
that in a sec.
Not to worry.
pipe($someObject, method('foo'), prop('bar'));
Or, more realistically, you'd use method() and prop() in a map or filter call within the pipe.
In this case, you want just the method/property name on its own, without a namespace, because it will be used in the context of an object to be named later.
So, for example, you could replace your pipe with:
pipe($someObject, method(nameof($someObject->foo(...))),
prop(nameof($someObject->bar)))It looks a bit wordy, but PHP differentiates between properties and
methods by using a parenthesis/bracket (hence if you want to call a
property that is actually a closure, you have to write ($this->prop)()
otherwise it will look for a method called prop and fail to find it).At runtime, you would receive a warning on this line if $someObject is
missing a method called foo or a property called bar (if the
error-detecting version is chosen). This would result likely result in
an actual error in your method() and prop() functions. However, it
should make debugging from logs a bit easier since you'll (hopefully)
notice the warning.
I figured it would be something like that. I don't know how it would be done better, but it also makes the code notably more verbose, and clumsy with the extra ()s. So, I'm not sure if it's a net-win or not.
Now, why I didn't answer you and only made it half way through your email...
Much of the earlier discussion was about functions/consts being
fully-qualified names. I was mostly against it, because I was offering
'both' qualified and unqualified. However, your initial examples
really hit home for me and I did a bit of spelunking in various
popular PHP codebases for class::name (since that is really the only
similar thing) and for stringified names of things. I'm now convinced
that FQL's should be returned for functions, and constants (but not
methods and properties).
Mission accomplished, then. :-) And yes, I agree with that conclusion.
Thus, your example:
Router::addRoute('\my\space\my_action_function`);
Router::addRoute(MyClass::actionMethod);could be rewritten as
use function \my\space\my_action_function;
Router::addRoute(nameof(my_action_function));
Router::addRoute(nameof(MyClass::actionMethod(...)));I'll have to think about how to word this in RFC, but I'll do so as
soon as I figure it out.
use function \my\space\my_action_function;
Router::addRoute(nameof(my_action_function));
Router::addRoute(MyClass::name . '::' . nameof(MyClass::actionMethod(...))); // or something since the class name is not returned with nameof().
The function one seems reasonable. The method version, though, looks super ugly and hard to implement. I'd be much more likely to just split it into two arguments and leave the second as a string, TBH.
Using a :: pseudo-constant suffix rather than a ()-based construct would help with the syntax clumsiness in both cases, but wouldn't entirely eliminate it. I don't have a really great answer here, unfortunately. I think I'd marginally prefer a ::name suffix at this point just for syntactic convenience, but I'm still undecided if the overall tradeoff is worth it. (For ::name, probably, as that would be a much lighter syntax impact for those unusual cases that it's needed.)
--Larry Garfield
Using a :: pseudo-constant suffix rather than a ()-based construct would help with the syntax clumsiness in both cases, but wouldn't entirely eliminate it. I don't have a really great answer here, unfortunately. I think I'd marginally prefer a ::name suffix at this point just for syntactic convenience, but I'm still undecided if the overall tradeoff is worth it. (For ::name, probably, as that would be a much lighter syntax impact for those unusual cases that it's needed.)
I believe Lydia suggested something like that, and while I kinda liked
the syntax (better than nameof() for wordy-looking bits), it just
looked weird to me. I'm not dismissing it out of hand though. There
might even be some middle ground where nameof() only works on
functions/constants, while methods/properties have a ::name pseudo
constant. I'll have to play with it and how it feels.
Hello Internals,
After much thought and deliberation, I've updated the nameof RFC
(https://wiki.php.net/rfc/nameof) to reflect most of this discussion
and refine a few edge cases regarding objects. TL;DR: there are a few
rules regarding how properties of objects are addressed that I hope a
few people will like (and would make it much more usable in static
contexts, like attributes).
I'm still not fully settled on the syntax, and this is still very much
a work in progress. So, I'd love feedback in that regard.
Robert Landers
Software Engineer
Utrecht NL
Am 14.01.2024 um 23:58 schrieb Robert Landers landers.robert@gmail.com:
Hello Internals,
After much thought and deliberation, I've updated the nameof RFC
(https://wiki.php.net/rfc/nameof) to reflect most of this discussion
and refine a few edge cases regarding objects. TL;DR: there are a few
rules regarding how properties of objects are addressed that I hope a
few people will like (and would make it much more usable in static
contexts, like attributes).I'm still not fully settled on the syntax, and this is still very much
a work in progress. So, I'd love feedback in that regard.Robert Landers
Software Engineer
Utrecht NL
Hey Robert,
In line with ::class, nameof() should never prepend a leading slash.
The leading slash is a compile time concept to distinguish namespace relative and global addressing. At runtime everything has a canonical name, without leading slash.
Also prepending a slash in code, i.e. '\'. is much easier than substr(..., 1) to remove, if needed.
On the name resolution of things which have a visibility, I wonder whether the visibility is checked or not? In e.g. C# it is.
Also, the RFC says a warning will be emitted during compilation. I assume it should be rather be the first time the given line is executed at runtime as PHP doesn't necessarily have access to all identifiers yet when the code is compiled (e.g. referencing something which is included at runtime).
Similarly, how often is static:: checked? On every access? Thanks to late static binding it may sometimes exist and sometimes not.
I think parent:: is missing in the list of left-hand allowed exceptions.
Thanks,
Bob