[PHP-DEV] [RFC] Strict Namespace Resolution

Hi all,

Per feedback from Tim Tim Düsterhus <[RFC] Function Autoloading (mark 5) - Externals; I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

- RFC: PHP: rfc:strict-namespace
- PR: Implement declare(strict_namespace=1) to disable global name fallback by pmjones · Pull Request #22736 · php/php-src · GitHub

(This RFC is an aid to, but not strictly required for, function autoloading.)

-- pmj

On 15 July 2026 01:54:32 BST, "Paul M. Jones" <pmjones@pmjones.io> wrote:

Hi all,

Per feedback from Tim Tim Düsterhus <[RFC] Function Autoloading (mark 5) - Externals; I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

I said this already on the other thread, but if we're going to do this, please let's come up with a better name.

"Strict" implies some extra check that namespaces are "correct" in some way, which isn't really what this is about. And without reading the manual (or RFC), I would genuinely have no idea if setting this to "1" meant "all namespaced names have to be qualified" or "all global names have to be qualified".

There's also at least two prior RFCs on this subject which we should look back at:

- PHP: rfc:fallback-to-root-scope-deprecation - 2017; proposed removing the fallback completely; discussed but never voted
- PHP: rfc:use_global_elements - 2020; proposed a declare for global-only lookups; Declined

There are probably other relevant threads in the list archives as well.

Regards,

Rowan Tommins
[IMSoP]

The RFC draft I put up that seems to have fell into the lost stack might be coupled with this. If I’m going to run that command, whatever it’s called, the namespace autoload process seems the logical place to do it to insure it is called before the namespace is invoked the first time. Otherwise you’ll have to have this declare at the top of every file in the namespace which could be awkward.

On Wed, Jul 15, 2026 at 5:14 AM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 15 July 2026 01:54:32 BST, “Paul M. Jones” <pmjones@pmjones.io> wrote:

Hi all,

Per feedback from Tim Tim Düsterhus <https://externals.io/message/131332#131899> I have extracted the declare(strict_namespace=1) portion of the function autoloading proposal to its own RFC.

I said this already on the other thread, but if we’re going to do this, please let’s come up with a better name.

“Strict” implies some extra check that namespaces are “correct” in some way, which isn’t really what this is about. And without reading the manual (or RFC), I would genuinely have no idea if setting this to “1” meant “all namespaced names have to be qualified” or “all global names have to be qualified”.

There’s also at least two prior RFCs on this subject which we should look back at:

There are probably other relevant threads in the list archives as well.

Regards,

Rowan Tommins
[IMSoP]

On 15 July 2026 13:48:07 BST, Michael Morris <tendoaki@gmail.com> wrote:

The RFC draft I put up that seems to have fell into the lost stack might be
coupled with this. If I'm going to run that command, whatever it's called,
the namespace autoload process seems the logical place to do it to insure
it is called before the namespace is invoked the first time. Otherwise
you'll have to have this declare at the top of every file in the namespace
which could be awkward.

Setting declare directives across multiple files is something that's come up many times over the years, but it needs a lot more than autoloading.

Right now, the compiler looks at one file at a time, and OpCache will reuse that compiled form whenever that same file is included.

For one file to influence how another file is compiled, you need a way to say either "this code file will *only ever* be loaded in the context of this setting file", or "the cache for this code file is *only valid* in the context of this setting file".

Either way, it's not enough to just say "declare this setting for any matching code files from now on", whether you have an autoloader callback or not.

In other words, this strays firmly into the "native package"/"module" design space.

Rowan Tommins
[IMSoP]

Hi Paul

···

On 15.07.26 02:54, Paul M. Jones wrote:

Per feedback from Tim Tim Düsterhus [<https://externals.io/message/131332#131899>](https://externals.io/message/131332#131899) I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

- RFC: [https://wiki.php.net/rfc/strict-namespace](https://wiki.php.net/rfc/strict-namespace)
- PR:  [https://github.com/php/php-src/pull/22736](https://github.com/php/php-src/pull/22736)

(This RFC is an aid to, but not strictly required for, function autoloading.)

This flag forces the use of a \ prefix, but it’s worth noting there are some communities (e.g. Laravel and Symfony) who have explicitly rejected the idea of prefixing all functions for readability reasons. We can force them into conforming, or forever keep the old behavior, but that’s probably not the best approach.

If PHP were designed today, what I would personally like is:

  • The rules for classes, functions and constants are equivalent.
  • The default is short / easy to type.
  • The rules are consistent inside and outside namespaces.
  • There is no fallback.

In practice, this would mean pretty much the opposite of this proposal:

  • new Foo/foo()/FOO always refers to the global symbol. If you want the local symbol, add a local use like for everything else.
  • The only conceivable exception could be a local class/function/constant declaration within the same file, which could conceptually add a use for you.
  • Global function calls (which is the vast majority of all function calls) can remain unprefixed, and references to global classes no longer need a prefix.
  • Class names behave equivalently inside and outside namespaces.
  • The lack of a fallback solves ambiguity issue that prevents many optimizations (https://externals.io/message/124718).

I’m not sure if there’s a world where we can still move into that direction, e.g. via a declare as a migration path.

Ilija

On 15 July 2026 16:54:04 BST, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

This flag forces the use of a \ prefix, but it's worth noting there are some communities (e.g. Laravel and Symfony) who have explicitly rejected the idea of prefixing all functions for readability reasons.

I know it won't change anyone's mind, but for the record, I've always found this baffling. Adding a single punctuation mark next to function calls seems far more readable to me than having a huge block of "use function" lines at the top of every file.

The often-suggested "scalar methods" would add more punctuation than moving to "no global fallback" - "\strlen($foo)" vs "$foo->length()".

Rowan Tommins
[IMSoP]

Hi Ilia,

On Jul 15, 2026, at 10:54, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

Hi Paul
On 15.07.26 02:54, Paul M. Jones wrote:

Per feedback from Tim Tim Düsterhus <[RFC] Function Autoloading (mark 5) - Externals; I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

- RFC: PHP: rfc:strict-namespace
- PR: Implement declare(strict_namespace=1) to disable global name fallback by pmjones · Pull Request #22736 · php/php-src · GitHub

(This RFC is an aid to, but not strictly required for, function autoloading.)

This flag forces the use of a \ prefix, but it's worth noting there are some communities (e.g. Laravel and Symfony) who have explicitly rejected the idea of prefixing all functions for readability reasons. We can force them into conforming, or forever keep the old behavior, but that's probably not the best approach.

I sympathize with that rejection -- I don't especially like prefixing everything with \ myself. Even having to import every global function explicitly, as required under some coding stndards rulesets, feels noisy to me at times.

But to be clear, `declare(strict_namespace=1)` is an opt-in behavior. If you want the global fallback behavior, no need to declare the directive. Further, I would not advise "forcing" anyone to use it who doesn't want it.

Have I maybe missed what you were getting at?

-- pmj

Hi Paul

I normally don’t correct people on my name, but just to avoid confusion: We now have myself and Ilia Alshanetsky being active again. Just want to point out that we’re two different people.

···

On 15.07.26 19:48, Paul M. Jones wrote:

Hi Ilia,
On Jul 15, 2026, at 10:54, Ilija Tovilo [<tovilo.ilija@gmail.com>](mailto:tovilo.ilija@gmail.com) wrote:

Hi Paul
On 15.07.26 02:54, Paul M. Jones wrote:

Per feedback from Tim Tim Düsterhus [<https://externals.io/message/131332#131899>](https://externals.io/message/131332#131899) I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

- RFC: [https://wiki.php.net/rfc/strict-namespace](https://wiki.php.net/rfc/strict-namespace)
- PR: [https://github.com/php/php-src/pull/22736](https://github.com/php/php-src/pull/22736)

(This RFC is an aid to, but not strictly required for, function autoloading.)


This flag forces the use of a \ prefix, but it's worth noting there are some communities (e.g. Laravel and Symfony) who have explicitly rejected the idea of prefixing all functions for readability reasons. We can force them into conforming, or forever keep the old behavior, but that's probably not the best approach.

I sympathize with that rejection -- I don't especially like prefixing everything with \ myself. Even having to import every global function explicitly, as required under some coding stndards rulesets, feels noisy to me at times.

But to be clear, `declare(strict_namespace=1)` is an opt-in behavior.  If you want the global fallback behavior, no need to declare the directive.  Further, I would not advise "forcing" anyone to use it who doesn't want it.

Have I maybe missed what you were getting at?

I understand, but the value-add of this declaration is very small if the plan isn’t ever to deprecate/remove the old behavior.

  • People who opt-in don’t gain anything new. They were already relying on the exact same behavior, with the fallback never triggering.
  • It might help with conformance, but not beyond what a linter could already do automatically.
  • The people who don’t opt-in won’t overcome the optimization limitations.
  • The ecosystem remains fragmented.

Hi Ilija,

On Jul 15, 2026, at 12:57, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

Hi Paul
On 15.07.26 19:48, Paul M. Jones wrote:

Hi Ilia,

I normally don't correct people on my name, but just to avoid confusion: We now have myself and Ilia Alshanetsky being active again. Just want to point out that we're two different people.

/me grimaces

You are right to correct me. It was a typo on my part; I apologize for failing to catch it before sending.

the value-add of this declaration is very small if the plan isn't ever to deprecate/remove the old behavior.

I can see why some might think that; even so, it is still a valuable addition (however small) even if the old behavior is never removed, in the same way that strict_types is valuable even though the old behavior has not been removed.

Further, note that this is an extraction from the function-autoloading RFC; its origin there was due to the shadow-global problem which (thankfully) turns out to be relatively rare. In those rare cases, this directive becomes an option to mitigate the situation.

-- pmj

On 15 July 2026 19:17:43 BST, "Paul M. Jones" <pmjones@pmjones.io> wrote:

I can see why some might think that; even so, it is still a valuable addition (however small) even if the old behavior is never removed, in the same way that strict_types is valuable even though the old behavior has not been removed.

I'm going to bang this drum again: neither mode of strict_types is older than the other. Both were designed at the same time, when scalar type declarations were added to the language, and the directive was to let users choose between two competing designs.

In hindsight, both the name of the directive, and the details of "mode 0", really needed more polish. Unfortunately, by then everyone was exhausted by several thousand emails arguing about the topic, and relieved that any sort of consensus had been reached.

That's why I'm so keen *not* to base the name and options of this feature (or any other) on the strict_types precedent.

If this is truly about offering choice, name it clearly as such, and offer all three modes (global-only, current-ns-only, fallback).

If the purpose is to somehow move towards removing the fallback completely, then we should explicitly work towards that goal, e.g.:

- name the directive to imply future vs past, e.g. legacy_name_fallback
- in 8.next, emit a deprecation in certain situations if neither legacy_name_fallback=1 or legacy_name_fallback=0 is set
- in 9.0, that could become an error, but both modes still available
- in 10.0, legacy_name_fallback=1 would become an error, and legacy_name_fallback=0 would be a no-op as it would be the default behaviour

Or, as proposed several years ago, just deprecate the behaviour directly and pick a new behaviour for all code as of PHP 9.0. People will complain about finding their backslash keys, but they'll get used to it.

Regards,

Rowan Tommins
[IMSoP]

Hi Rowan,

On Jul 15, 2026, at 17:23, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 15 July 2026 19:17:43 BST, "Paul M. Jones" <pmjones@pmjones.io> wrote:

I can see why some might think that; even so, it is still a valuable addition (however small) even if the old behavior is never removed, in the same way that strict_types is valuable even though the old behavior has not been removed.

I'm going to bang this drum again: neither mode of strict_types is older than the other. Both were designed at the same time, when scalar type declarations were added to the language, and the directive was to let users choose between two competing designs.

In hindsight, both the name of the directive, and the details of "mode 0", really needed more polish. Unfortunately, by then everyone was exhausted by several thousand emails arguing about the topic, and relieved that any sort of consensus had been reached.

That's why I'm so keen *not* to base the name and options of this feature (or any other) on the strict_types precedent.

A reasonable stand, and thanks for the historical context.

If this is truly about offering choice, name it clearly as such, and offer all three modes (global-only, current-ns-only, fallback).

If the purpose is to somehow move towards removing the fallback completely, then we should explicitly work towards that goal

To be clear, it is driven neither by "offering choice" nor by "removing the fallback entirely". It is driven by offering a way to mitigate the shadow-global case in the Function Autoloading RFC. As it turns out, that case is relatively rare, but Tim opined this feature should be separate from Function Autoloading, so here we are.

* * *

Having said all of that, I am good with *some* form of a directive that helps with the rare but not non-existent shadow-global case.

As this RFC stands now, is the directive name alone the main blocker for you, or is it also the values?

Thank you for your patient attention.

-- pmj

Le 16/07/2026 à 00:23, Rowan Tommins [IMSoP] a écrit :

On 15 July 2026 19:17:43 BST, "Paul M. Jones" <pmjones@pmjones.io> wrote:

I can see why some might think that; even so, it is still a valuable addition (however small) even if the old behavior is never removed, in the same way that strict_types is valuable even though the old behavior has not been removed.

I'm going to bang this drum again: neither mode of strict_types is older than the other. Both were designed at the same time, when scalar type declarations were added to the language, and the directive was to let users choose between two competing designs.

In hindsight, both the name of the directive, and the details of "mode 0", really needed more polish. Unfortunately, by then everyone was exhausted by several thousand emails arguing about the topic, and relieved that any sort of consensus had been reached.

That's why I'm so keen *not* to base the name and options of this feature (or any other) on the strict_types precedent.

If this is truly about offering choice, name it clearly as such, and offer all three modes (global-only, current-ns-only, fallback).

If the purpose is to somehow move towards removing the fallback completely, then we should explicitly work towards that goal, e.g.:

- name the directive to imply future vs past, e.g. legacy_name_fallback
- in 8.next, emit a deprecation in certain situations if neither legacy_name_fallback=1 or legacy_name_fallback=0 is set
- in 9.0, that could become an error, but both modes still available
- in 10.0, legacy_name_fallback=1 would become an error, and legacy_name_fallback=0 would be a no-op as it would be the default behaviour

Or, as proposed several years ago, just deprecate the behaviour directly and pick a new behaviour for all code as of PHP 9.0. People will complain about finding their backslash keys, but they'll get used to it.

Regards,

Rowan Tommins
[IMSoP]

I agree with the fact that "strict_namespaces" is confusing: we're not talking about a namespace being strict, but about a function name reference to fall back to global scope if called in a namespace context. Naming it "legacy" implies something temporary, which I both like and dislike (because of the old adage "there's nothing more permanent than a temporary solution"), which can also be confusing in some eyes.

The whole concept (aka "not falling back to global scope") is something I would like to see too, because I often configure my CS fixers to either prepend the "\" for all function/const calls, or add "use const" and "use function" statements on top of the file. CS fixers could detect this quite easily in an AST: either the function is fully-qualified (then the fixer does nothing), and if it's not, rely on either "add root ns" or "add use stmt on top of the file" to fix it. It's already the case today, and in the future, it could include a third option "add declare stmt" to ensure no fallback is ever used anyway.

I would be picky on the naming, and maybe suggest something like "disable_root_ns_fallback=0|1" ?

Hi

On 2026-07-15 02:54, Paul M. Jones wrote:

Per feedback from Tim Tim Düsterhus <[RFC] Function Autoloading (mark 5) - Externals; I have extracted the `declare(strict_namespace=1)` portion of the function autoloading proposal to its own RFC.

- RFC: PHP: rfc:strict-namespace
- PR: Implement declare(strict_namespace=1) to disable global name fallback by pmjones · Pull Request #22736 · php/php-src · GitHub

Thank you for the RFC. I'm, probably unsurprisingly, in favor.

However I agree with Rowan that the naming of the directive could be made clearer. I could imagine `declare(global_fallback=0)` (with 1 being the default) being a reasonable option between clarity and brevity.

With regard to the “RFC Impact” section, the “None” for the ecosystem is not quite accurate: Static analyzers and IDEs will want to learn about the directive to

1. Emit diagnostics when a function or constant is not explicitly imported and doesn't exist in the namespace.
2. Emit diagnostics when the directive is not used despite being configured as “desirable” to make sure it is consistently applied within an entire project.

This kind of “work being imposed because otherwise the tools would be worse than they could be” on downstream projects is the main goal of the “Ecosystem Impact” section, obvious compatibility breaks are already listed in the “Backward Incompatible Changes” section :slight_smile:

Best regards
Tim Düsterhus

Hi

On 2026-07-15 17:54, Ilija Tovilo wrote:

* The rules for classes, functions and constants are equivalent.

Agreed.

* The default is short / easy to type.

Agreed, but I'm probably drawing a different conclusion than you, see below.

* The rules are consistent inside and outside namespaces.

Agreed.

* There is no fallback.

Agreed.

In practice, this would mean pretty much the opposite of this proposal:

* `new Foo`/foo()/FOO always refers to the global symbol. If you want
   the local symbol, add a local use like for everything else.

I believe that “unqualified names are relative” is both an obvious, and a pragmatic choice. Given that a majority of class-based code is namespaced, global classes are rarely used, whereas it's often the case that related classes from the same namespace are required. An obvious example would be exception classes, which are colocated within the same namespaces under the Throwable policy of PHP.

* Global function calls (which is the vast majority of all function
   calls) can remain unprefixed, and references to global classes no
   longer need a prefix.

While this is true today for large parts of PHP’s standard library, our policy strongly recommends the use of namespaces for “new components” and this includes functions and constants. The new DOM API in PHP 8.4 aliased many of the global constants into the `Dom\*` namespace, and there's also `Dom\import_simplexml()`. Similarly, I'm expecting that the new date and time API will include some functions in addition to the OO-API and these would also be put into the `Time\*` namespace. One example I can think of would be a `Time\now(): Instant` function for easy retrieval of an instant from the system clock. Ideas for a new iterable-API also suggested the use of a `iterable\*` namespace, e.g. `iterable\map()` (for `array_map()`) or `iterable\filter()` (for `array_filter()`).

Thus PHP developers already need to be prepared to explicitly import or fully-qualify function calls and any change we're making should be reasonably “forward looking” with regard to how the standard library is going to be modernized.

Best regards
Tim Düsterhus

Hi

On 2026-07-15 19:18, Rowan Tommins [IMSoP] wrote:

On 15 July 2026 16:54:04 BST, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

This flag forces the use of a \ prefix, but it's worth noting there are some communities (e.g. Laravel and Symfony) who have explicitly rejected the idea of prefixing all functions for readability reasons.

I know it won't change anyone's mind, but for the record, I've always found this baffling. Adding a single punctuation mark next to function calls seems far more readable to me than having a huge block of "use function" lines at the top of every file.

FWIW, I agree. In Visual Studio Code it's also possible to adjust the styling of namespaces specifically, which would allow tuning down the backslash:

As an example, this config renders namespaces in black with 50% opacity:

     "editor.tokenColorCustomizations": {
         "textMateRules": [
             {
                 "scope": "support.other.namespace.php",
                 "settings": {
                     "foreground": "#0000007F"
                 }
             }
         ]
     }

Using `punctuation.separator.inheritance.php` as the scope will just affect the backslash specifically.

Best regards
Tim Düsterhus

Hi all,

Primary pushback thus far has not been on the functionality per se but on the directive name itself. The currently-suggested equivalent alternatives are:

strict_namespace=1            (RFC as-is)
legacy_name_fallback=0        (R. Tommins)
global_fallback=0             (T. Düsterhus)
disable_root_ns_fallback=1    (A. Rock)

Of those, I'd be OK with `global_fallback=0` (though I do still prefer `strict_namespace=1`).

Are there any further suggestions or objections on this point?

-- pmj

On Fri, July 17 2026 at 12:39, Paul M. Jones wrote:

Primary pushback thus far has not been on the functionality per se but on the directive name itself. The currently-suggested equivalent alternatives are:

    strict_namespace=1 (RFC as-is)
    legacy_name_fallback=0 (R. Tommins)
    global_fallback=0 (T. Düsterhus)
    disable_root_ns_fallback=1 (A. Rock)

Of those, I'd be OK with `global_fallback=0` (though I do still prefer `strict_namespace=1`).

Are there any further suggestions or objections on this point?

Hi Paul,

Thank you for working on this RFC! It's essentially the same as my `declare(root_fallback=0)` idea from 2019. [1]

I think the `global_fallback=0` alternative from Tim indicates the resulting behavior most clearly, but I don't have a strong preference between this and `strict_namespace=1`.

-Theodore

[1]: High performance function autoloading - Externals

Hi Theodore,

On Jul 17, 2026, at 16:41, Theodore Brown <theodorejb@outlook.com> wrote:

On Fri, July 17 2026 at 12:39, Paul M. Jones wrote:

Primary pushback thus far has not been on the functionality per se but on the directive name itself. The currently-suggested equivalent alternatives are:

   strict_namespace=1 (RFC as-is)
   legacy_name_fallback=0 (R. Tommins)
   global_fallback=0 (T. Düsterhus)
   disable_root_ns_fallback=1 (A. Rock)

Of those, I'd be OK with `global_fallback=0` (though I do still prefer `strict_namespace=1`).

Are there any further suggestions or objections on this point?

Hi Paul,

Thank you for working on this RFC! It's essentially the same as my `declare(root_fallback=0)` idea from 2019. [1]

Ah so -- my bad for not including it in the prior art on this RFC.

I think the `global_fallback=0` alternative from Tim indicates the resulting behavior most clearly, but I don't have a strong preference between this and `strict_namespace=1`.

Very good!

-- pmj

Am 17.07.2026, 20:39:00 schrieb Paul M. Jones <pmjones@pmjones.io>:

Hi all,

Primary pushback thus far has not been on the functionality per se but on the directive name itself. The currently-suggested equivalent alternatives are:

strict_namespace=1 (RFC as-is)
legacy_name_fallback=0 (R. Tommins)
global_fallback=0 (T. Düsterhus)
disable_root_ns_fallback=1 (A. Rock)

Of those, I’d be OK with global_fallback=0 (though I do still prefer strict_namespace=1).

In my opinion it makes sense to vote on the concept and then have a secondary vote on the name.

I‘d argue everyone agrees we need this but with only one option it might fail.

Are there any further suggestions or objections on this point?

– pmj

On 18/07/2026 19:08, Paul M. Jones wrote:

Thank you for working on this RFC! It's essentially the same as my `declare(root_fallback=0)` idea from 2019. [1]

Ah so -- my bad for not including it in the prior art on this RFC.

I pointed out a couple of other prior discussions earlier: [RFC] Strict Namespace Resolution - Externals

Most notably, one of those is an RFC which went to vote and was almost-unanimously declined, so it would be really good to add something to the RFC comparing your proposal with Tyson's.

--
Rowan Tommins
[IMSoP]