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.
(This RFC is an aid to, but not strictly required for, function autoloading.)
-- pmj
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:
- https://wiki.php.net/rfc/fallback-to-root-scope-deprecation - 2017; proposed removing the fallback completely; discussed but never voted
- https://wiki.php.net/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:
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:
- https://wiki.php.net/rfc/fallback-to-root-scope-deprecation - 2017;
proposed removing the fallback completely; discussed but never voted- https://wiki.php.net/rfc/use_global_elements - 2020; proposed a declare
for global-only lookups; DeclinedThere 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.
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
Per feedback from Tim Tim Düsterhushttps://externals.io/message/131332#131899 I have extracted the
declare(strict_namespace=1)portion of the function autoloading proposal to its own RFC.(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
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
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 Ilia,
Hi Paul
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.(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
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.Hi Paul
Per feedback from Tim Tim Düsterhushttps://externals.io/message/131332#131899 I have extracted the
declare(strict_namespace=1)portion of the function autoloading proposal to its own RFC.(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,
Hi Paul
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
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,
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 :
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
- 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
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.
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
- Emit diagnostics when a function or constant is not explicitly
imported and doesn't exist in the namespace. - 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 :-)
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
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 preferstrict_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
Hi Theodore,
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 preferstrict_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=0alternative from Tim indicates the resulting behavior most clearly, but I don't have a strong preference between this andstrict_namespace=1.
Very good!
-- pmj
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:
https://externals.io/message/131929#131932
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]
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
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.
Firstly, I'm not sure it's true that "everyone agrees we need this". I'm
not totally convinced that a per-file declare() is the right solution to
this problem; and there's clear disagreement about whether "always
global" or "always current NS" makes the most sense.
Secondly, a split vote leaves voters who actively dislike a particular
name with an awkward choice: vote Yes, and risk the "bad" name being
chosen; or vote No, even though you would support the feature under a
different name.
I think the name here is more than just a detail, it really changes the
perception of the whole feature.
--
Rowan Tommins
[IMSoP]