rfc:nameof

This is an old revision of the document!


PHP RFC: NameOf

Introduction

Currently, it is possible to inspect virtually any aspect of running code, except there is no easy way to inspect the locally executing scope. For example, a developer can get the class of an object with get_class() or the name with MyClass::class or $myClass::class. A developer can get the name of a parameter or property via reflection, however, when a developer needs to write a simple error message, that may be overkill, thus the variable name is usually hard-coded into the error message. This usually works fine until a developer refactors the code and forgets to change the name of the variable in the error message.

nameof() allows developers to quickly and easily access the name of a variable in the current scope, in a way that keeps error messages consistent.

This RFC proposes a global nameof() function that allows a developer to get the name of virtually any user-named variable, property, const, or member, quickly and easily:

echo nameof($variable); // variable
echo nameof($object->property); // property
echo nameof(Enum::Case); // Case
echo nameof(Object::Const); // Const
echo nameof(myFunction(...)); // myFunction
echo nameof(MY_CONSTANT); // MY_CONSTANT

As named parameters become more prevalent, providing the end user with the name of the parameter is more important, in a way that it is easy to locate when refactoring, especially as part of a deprecation strategy:

function myAwesomeFunc(mixed $param1 = null, int $param2 = 0) {
  // potentially many lines later...
  if(is_called_with_deprecated_value($oldParam)) {
    $logger->warn(nameof($oldParam) . ' is deprecated in the next version, please use ' . nameof($newParam);
  }
}

Further, using nameof() would allow simpler usage of attributes, by using the silence operator (see error checking):

// evaluates to #[EventHandler(callback: 'handler')]
#[EventHandler(callback: @nameof(Operator::handler))]
class Operator {
  public function handler() {
    // implementation
  }
}

It's important to note that some things have an ambiguous name that is a compile error:

echo nameof($a[$b]); // is it a or b? (can call nameof($a) or nameof($b) instead)
echo nameof($a === $b); // ??
echo nameof($a($b)); // same as array access

All of these will generate a compile error: “Cannot get the name of an ambiguous expression.”

Proposal

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). This means that there is an 'identity' for every valid nameof():

${nameof($variable)} === $variable;
$object->{nameof($object->property)} === $object->property;
constant(Enum::class . '::' . nameof(Enum::Case)) === Enum::Case;
constant(Object::class . '::' . nameof(Object::Const)) === Object::Const;
(nameof(myFunction(...))() === myFunction();
constant(nameof(MY_CONSTANT)) === MY_CONSTANT;

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:

namespace Name {
    const MY_CONST = true;
    function namedFunction() {}
}
 
namespace Other {
    echo nameof(\Name\MY_CONST); // \Name\MY_CONST
    echo nameof(\Name\namedFunction(...)); // \Name\namedFunction
}
 
namespace Using {
    use const Name\MY_CONST as ;
    use const Name\MY_CONST as ALIASED_CONST;
    use function Name\namedFunction;
 
    echo nameof(MY_CONST); // MY_CONST
    echo nameof(\Name\MY_CONST); // \Name\MY_CONST
    echo nameof(ALIASED_CONST); // ALIASED_CONST
    echo nameof(namedFunction(...)); // namedFunction
    echo nameof(\Name\namedFunction(...)); // \Name\namedFunction(...)
}

Error Handling

There are several ways to handle errors, each one has its own merits but ultimately will be left up as a secondary vote:

1. No error handling

In this case, there is no error handling. This would allow using nameof on things that don't exist. For example, no error would be output if a developer references a static property on an instanced property:

class MyClass {
  public string $var;
}
nameof(MyClass::var); // no error, returns 'var'
nameof($aVariable); // no error, returns 'aVariable'

The downside is that PHP won't natively inform a developer that they've used something that doesn't exist. The main benefit of this approach is that it would allow the community to come up with its own rules via static analysis tools.

2. Warnings as usual

In this case, PHP would output warnings and errors a developer would expect to see if they used something that doesn't exist:

class MyClass {
  public string $var;
}
nameof(MyClass::var); // returns 'var'
// Uncaught Error: Undefined constant MyClass::var
nameof($aVariable); // returns 'aVariable'
// Warning: Undefined variable $aVariable

3. Warnings and a way to disable them

Warnings/errors can be disabled via the 'silence operator' (@) to behave just like the first case. This is the best of both worlds, allowing developers to 'break the rules' to specify properties/functions without a reference to the object (@nameof(Object::method(...)) vs. nameof($object->method)) but would require assistance via static analysis tooling to catch mistakes.

Backward Incompatible Changes

There are no backward incompatible changes at this time except that `nameof` will become a reserved word.

Future Scope

This could be expanded in the future to allow classes and types. For example, nameof(int) or nameof(MyClass)

Proposed PHP Version(s)

  • 8.3: release
  • 8.2.x: nameof becomes a reserved word, emitting a notice

RFC Impact

To SAPIs

Should be none

To Existing Extensions

Will existing extensions be affected? No

To Opcache

The function call is compiled to a string.

New Constants

None

Open Issues

- Should first-class callables be allowed? It's relatively easy to get the name of a first-class callable and only included for completeness.

Unaffected PHP Functionality

PHP will largely be unaffected by this change, except that a new global function is introduced.

Future Scope

None.

Proposed Voting Choices

This is a simple yes-or-no vote to include this feature. 2/3 majority required to pass.

There is a subvote for the type of error checking to include. Majority wins.

Patches and Tests

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

Links to external references, discussions or RFCs

Rejected Features

Keep this updated with features that were discussed on the mail lists.

rfc/nameof.1683923736.txt.gz · Last modified: 2023/05/12 20:35 by withinboredom