rfc:use_global_elements

PHP RFC: "use global functions/consts" statement

Introduction

When calling a function or using a constant that isn't fully qualified from a namespace (other than the global namespace), PHP will check these two locations in order:

  1. The current namespace.
  2. The global namespace. (if not found in the current namespace)

This leads to the following problems:

  1. A minor decrease in performance, due to not being able to use specialized opcodes (and other optimizations) for functions such as strlen due to the ambiguity, not being able to evaluate the values of constants,
    and due to usually needing to check the current namespace before finding a function/constant in the global namespace at runtime.

    For example, version_compare(PHP_VERSION, '7.0.0', '>=') can't be converted to a constant by opcache in a namespace,
    but \version_compare(\PHP_VERSION, '7.0.0', '>=') can.
  2. Developers having to deal with ambiguity of which function is being called, if a project has or uses namespaced functions.

In order to eliminate the ambiguity, there are currently several options, which have different drawbacks:

  1. Add multiple use function function_name and use const MY_CONST at the top of the namespace.
    This is prone to merge conflicts when functions start/stop being used, inconvenient to keep up to date, and the vast majority of these will be global functions and constants.
  2. Write functions as \function_name() and constants as \MY_CONST.
    • This is more verbose, and inconvenient if the only types of constants/functions used are global.
    • Some builtins such as __DIR__ and empty() aren't actually constants/functions, and they can't be prefixed by backslashes.
    • Version control history for lines would change if MY_CONST was replaced with \MY_CONST
    • New contributors to a project would often be unfamiliar with any applicable coding style guideline of fully qualifying function/const uses.
  3. Avoid using namespaces (this is forbidden by PSR-1)
  4. Mixes of the above approaches.

This RFC proposes a new language feature which avoids those drawbacks.

Proposal

Support the following statements within namespaces: use global functions; and use global consts;. These can be used in combination with other function/global uses, inside of each namespace block.

namespace MyNS;
 
use global functions;
use function OtherNS\my_function; // can occur before or after "use global functions;"
 
use global consts;
use const OtherNS\OTHER_CONST;
 
// The below function and constant references now unambiguously refer to the global namespace.
//
// Without "use global ...", php would have checked for MyNS\version_compare
// and MyNS\PHP_VERSION at runtime before checking the global namespace.
if (version_compare(PHP_VERSION, '8.0.5') >= 0) {
    // ...
}

Implementation Details

use global functions can be thought of behaving the same way as if every possible function name and constant name was used from the global namespace (for names not used elsewhere).

  • use global functions and use global consts; will have an effect on subsequent statements within the namespace block, but not on prior statements.
  • use global functions and use global consts; can be used in the same places as use const/function element_name;, and can occur before and/or after use const/function element_name;
  • The tokens consts and functions are case-insensitive, just like const and function are.

There is a working implementation at https://github.com/php/php-src/pull/4951

Grammar

Two new token types are added (and available through the tokenizer extension): T_FUNCTIONS (“functions”) and T_CONSTS (“consts”).

The only new statements added to the language grammar are use global functions; and use global consts;, i.e.

use_global: T_USE T_GLOBAL global_use_type;

global_use_type: T_FUNCTIONS
               | T_CONSTS;

Error handling

The error handling (for the first voting options) can be thought of being the same as if every possible function name and constant name was used from the global namespace (for names not used elsewhere).

  • A fatal error is caused by declaring a function in a namespace block with use global functions; following that statement.
  • A fatal error is caused by declaring a constant with const MY_CONST = … in a namespace block with use global consts; following that statement.

define() is not affected.

  • A fatal error is caused by repeating use global <element_type>s twice for the same <element_type> within the same namespace block.
  • A warning is emitted if use global <element_type>s; and use <element_type> global_element_name are both used, due to the latter being redundant when referring to the global namespace.
  • A warning is emitted if use global <element_type>s; is used in the global namespace.

Backward Incompatible Changes

The tokens “functions” and “consts” can no longer be used in the places where “function” and “const” couldn't be used (e.g. global function names and global constant names) (e.g. 'function functions() {} and const consts = 2; are now invalid, but they can still be used as method names)

  • I assume that adding a new token is preferable to using the generic name token (T_NAME) and rejecting invalid values when parsing (e.g. easier to suggest the expected token for syntax errors). But I'm not certain of that.

Apart from the impact of adding 2 new token types, this feature is opt-in and does not impact existing code.

Proposed PHP Version(s)

8.0

Proposed Voting Choices

Yes/No Vote

(Requires 2/3 vote to pass)

Feature Syntax

(The option with the most votes will be implemented. In the case of ties for these choices, the earlier option will be preferred.)

  1. use global functions; and use global consts; in namespace blocks.
  2. declare(lookup_functions_in_current_namespace = false, lookup_consts_in_global_namespace = false) in the file's declare() blocks.

Severity of redundant uses

When use global <element_type>s; and use <element_type> global_element_name; are both used in a namespace block, due to the latter being redundant when referring to the global namespace, php will do the following when the file is required:

  1. Warning (as described in the RFC)
  2. Fatal Error
  3. Allow it and don't warn

If “Allow it and don't warn” has over 50% of the votes, that option wins.

Otherwise, the option of “Warning” or “Fatal Error” with the most votes wins.

Severity of "use global elements;" from the global namespace

  1. Warning (as described in the RFC)
  2. Fatal Error
  3. Allow it and don't warn

If “Allow it and don't warn” has over 50% of the votes, that option wins.

Otherwise, the option of “Warning” or “Fatal Error” with the most votes wins.

The motivation for warning: PHP emits a warning such as Warning: The use statement with non-compound name 'strlen' has no effect in %s on line %d for use function strlen; from the global namespace.

Patches and Tests

Discussion

Arguments for declare()

From https://externals.io/message/107877#107883

That [use global functions] still doesn't really explain what's happening, because in code that doesn't use any namespaced functions, the line has no user-visible effect at all - functions are always used from the global namespace. What it actually does is switch off a language feature, so perhaps it should be something more like:

declare(lookup_functions_in_current_namespace=false);

That would also mean that it can be covered by any future way of setting language directives, such as settings per “module”, bundling into “editions”, etc.

From https://externals.io/message/107877#107894

  1. strict_types is not the first or only declare directive. Declaring an encoding fundamentally changes the meaning of a source file; probably it would be invalid in a different encoding, but errors are not the primary purpose of the directive. Declaring a value of “ticks” actually changes the runtime behaviour of the code. The manual defines declare vaguely as “used to set execution directives”, so it's not particularly clear to me that changing namespace resolution would be outside of its purpose.
  2. The existing fallback to global scope means that looking at the use statements of a file is not sufficient to resolve the ambiguity of an unprefixed function name. Indeed, the same line of code can execute two different functions within a running program if the namespaced function is defined at the right time.

From https://externals.io/message/107877#107894

I'm generally not convinced that beginning the special directive with the word “use” automatically makes it easier to find or understand. Given some code mentioning “strpos”, you wouldn't be able to scan the list of use statements for the word “strpos”, you'd have to understand that there are two modes of execution, and look for a line that switches between those modes.

Arguments for use global functions/consts

  • This is similar to the use function …; syntax. Both use global functions and declare(…) make it clear that this syntax can be used only for the global namespace.
  • use global functions; can be placed immediately adjacent to other use function …; statements, so only one block of code needs to be checked for function/constant resolution behavior (if coding style guidelines are enforced).
  • This can be set per each namespace block, in the uncommon case of files that combine blocks from multiple namespaces. This makes it possible for a file to use this in some namespace blocks, but also declare functions/constants in different namespace blocks.

Deprecate the fallback to the root namespace instead?

An earlier RFC https://wiki.php.net/rfc/fallback-to-root-scope-deprecation had the following notable objections, and I'm not sure of the current status of that RFC:

  • Deprecating the behavior of existing code would break (possibly unmaintained) third-party libraries and require a lot of code changes, discouraging updates

    use global functions; wouldn't.
  • Some mocking libraries will declare functions in namespaces being tested, with the same name as the global function.
    My proposal has similar drawbacks - third party code that started using “use function *” would break those libraries (but so would manually adding the same namespace uses).

    But there are alternatives to those libraries, such as uopz, runkit7, and SoftMocks (SoftMocks changes the class autoloader to replace code with instrumented code).

Any work on that RFC can be done independently of this RFC.

Encourage static methods and class constants instead

Arguments for: - Classes don't have the ambiguity functions and constants do, and already have autoloaders, so we should encourage use of classes instead. - Can potentially support use function Ns\SomeClass::some_method;, use const Ns\SomeClass::SOME_CLASS_CONST instead

Arguments against: - PHP will still need to support existing php modules and userland libraries/polyfills that use global functions/constants.

Deprecate fallback to the root namespace instead?

(i.e. for unqualified my_function(), do the opposite of this: work an approaches to only check \NS\my_function() without checking \my_function() to avoid ambiguity.)

This was suggested in combination with other strategies. See the section “Use module/library systems instead”.

Use module/library systems instead

We might eventually benefit from versioned “libraries” of functions that can be imported in one command which would solve many-a-future-problem by itself.

I'd be in favor of a module system, I just can't think of a good way to migrate to that with existing use cases and limitations. I'd imagine there would be various obstacles/objections to getting an RFC for that approach accepted. (continuing to support polyfills and autoloading; increasing ambiguity instead of decreasing ambiguity; any implementations I can think of would have worse performance with opcache)

If it is implemented in a way causing large backwards compatibility breaks, it may discourage upgrading.

See https://externals.io/message/107953#107962 for more details.

The performance hit is minor

It is in most cases. Counterarguments:

  • Making it easier to avoid ambiguity has benefits elsewhere - for analyzing code, future work on autoloading, helping developers avoid unexpected behavior in edge cases in applications, etc.
  • It's much larger in some cases than others due to other opcache optimizations, frequency of calling a line of code, etc. Benchmarking and interpreting performance of applications after new features get added may take more developer time than ensuring files contain use global functions; and use global consts;
  • -Developers may still aim to avoid ambiguity without this RFC, resulting in more verbose code.

Implementation

References

rfc/use_global_elements.txt · Last modified: 2020/01/14 19:37 by nikic