RFC: Namespace-Scoped Visibility for Methods and Properties

php.internals

Rob Landers

297 days ago
Hello Internals, I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. Sincerely, Rob Landers

Kamil Tekiela

297 days ago
On Sat, 8 Nov 2025 at 12:44, Rob Landers <rob@bottled.codes> wrote:
> > Hello Internals, > > I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). > > This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. > > Sincerely, > > Rob Landers
I like the idea except for the name. Why not use the internal keyword like C# does?

Rob Landers

297 days ago
On Sat, Nov 8, 2025, at 14:30, Kamil Tekiela wrote:
> On Sat, 8 Nov 2025 at 12:44, Rob Landers <rob@bottled.codes> wrote: > > > > Hello Internals, > > > > I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). > > > > This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. > > > > Sincerely, > > > > Rob Landers > > I like the idea except for the name. Why not use the internal keyword > like C# does? >
Hi, thanks for the feedback. The main reason I didn’t choose "internal" is that it implies some broader boundary than PHP currently defines. In languages like C#/Kotlin/Swift, "internal" is tied to a module or assembly, not a namespace. PHP doesn’t have a formal module/package boundary today, and I really prefer not to turn this RFC into a discussion about defining one. private(namespace) describes exactly what it does: it widens private to code in the same namespace and nothing beyond that. It doesn’t imply any other boundary or hierarchy. It’s also consistent with asymmetric visibility (public(set)/private(set)/etc), without introducing a new keyword. If we ever introduce modules or packages in the future, internal could certainly be layered on top, or become syntactic sugar over private(namespace) — or some part of it. Much like readonly is now almost syntactic sugar over "public private(set)", other than the write-once restriction. For now, I’m aiming to focus on making internal APIs easier to express without inventing a new packaging system. — Rob

Rowan Tommins [IMSoP]

297 days ago
On 08/11/2025 12:43, Rob Landers wrote:
> I’d like to introduce an RFC for discussion: > https://wiki.php.net/rfc/namespace_visibility which proposes a new > visibility modifier: private(namespace).
Hi Rob, Thanks for putting together the RFC. My main concern with this and similar single-keyword proposals is that its only useful if people lay out their code in a particular way, rather than fitting with the variety of namespace hierarchies seen in the wild. For instance, in your example, you have two classes: - App\Auth\SessionManager - App\Auth\SessionStore But if this was library with a set of interchangeable session stores, it might well lay them out like this: - Acme\AuthLib\Session\Manager - Acme\AuthLib\Session\Store\SessionStoreInterface - Acme\AuthLib\Session\Store\DatabaseSessionStore - Acme\AuthLib\Session\Store\FileSystemSessionStore - etc In that case, "current namespace plus children" would work, and I'd be interested in your reasoning for requiring an exact match instead. But even that might not be enough, if for some reason it looked like this: - Acme\AuthLib\Services\SessionManager - Acme\AuthLib\Implementations\SessionStore\SessionStoreInterface - Acme\AuthLib\Implementations\SessionStore\DatabaseSessionStore - Acme\AuthLib\Implementations\SessionStore\FileSystemSessionStore - etc Here, what we want is visibilty for everything inside "Acme\AuthLib", not only in "Acme\AuthLib\Services". Since, as you say in another reply, we don't have a standard definition of "module", "package", or "assembly", I think we need a keyword or attribute which takes as a parameter either the namespace prefix, or the number of levels to match.
-- Rowan Tommins [IMSoP]

Rob Landers

297 days ago
On Sat, Nov 8, 2025, at 17:50, Rowan Tommins [IMSoP] wrote:
> On 08/11/2025 12:43, Rob Landers wrote: > > I’d like to introduce an RFC for discussion: > > https://wiki.php.net/rfc/namespace_visibility which proposes a new > > visibility modifier: private(namespace). > > > Hi Rob, > > Thanks for putting together the RFC. > > My main concern with this and similar single-keyword proposals is that > its only useful if people lay out their code in a particular way, rather > than fitting with the variety of namespace hierarchies seen in the wild. > > For instance, in your example, you have two classes: > > - App\Auth\SessionManager > - App\Auth\SessionStore > > But if this was library with a set of interchangeable session stores, it > might well lay them out like this: > > - Acme\AuthLib\Session\Manager > - Acme\AuthLib\Session\Store\SessionStoreInterface > - Acme\AuthLib\Session\Store\DatabaseSessionStore > - Acme\AuthLib\Session\Store\FileSystemSessionStore > - etc > > In that case, "current namespace plus children" would work, and I'd be > interested in your reasoning for requiring an exact match instead. > > > But even that might not be enough, if for some reason it looked like this: > > - Acme\AuthLib\Services\SessionManager > - Acme\AuthLib\Implementations\SessionStore\SessionStoreInterface > - Acme\AuthLib\Implementations\SessionStore\DatabaseSessionStore > - Acme\AuthLib\Implementations\SessionStore\FileSystemSessionStore > - etc > > Here, what we want is visibilty for everything inside "Acme\AuthLib", > not only in "Acme\AuthLib\Services". > > Since, as you say in another reply, we don't have a standard definition > of "module", "package", or "assembly", I think we need a keyword or > attribute which takes as a parameter either the namespace prefix, or the > number of levels to match. > > > -- > Rowan Tommins > [IMSoP] >
Hi Rowan, Thanks for the detailed feedback. The exact-match requirement is intentional rather than incidental. In my nested classes RFC, I assumed namespaces behaved hierarchically, and it was pointed out that isn’t always guaranteed. So, one of the main design goals with this RFC was to make a boundary that was crisp and unsurprising: if two pieces of code have the same lexical namespace, access is allowed; if not, it isn’t. There's no need to consider namespace depth, prefixes, or directory layout. The moment we allow "namespace prefixes", we introduce questions that need to be fleshed out separately and with care. Do we treat "Acme\AuthLib\Session" and "Acme\AuthLib\Services" as related? Even if they come from different vendors and are unrelated in any way? Are namespaces hierarchical or just flat strings that happen to contain separators? Today namespaces are a name resolution mechanism, not a semantic hierarchy. Matching by prefix would start treating them as a package system, and I explicitly am trying to avoid that in this RFC. By restricting the rule to exact namespace equality, the feature is straightforward to explain and to understand. It also prevents "accidental" access because two unrelated namespaces happen to share a prefix. If we ever define a module/package boundary in PHP, a recursive or prefix-based visibility could be layered on top. For this RFC, I’m aiming for a feature that provides enforceable encapsulation using the boundaries PHP already defines, albeit, loosely through name resolution. If you have a concrete, unambiguous rule for prefix-based access that wouldn’t cause surprises or make assumptions about how people organise their codebases, I’d be happy to discuss it. Right now, every version I’ve explored would reopen the "module"/"package" debate from this summer. — Rob

Rowan Tommins [IMSoP]

296 days ago
On 08/11/2025 20:08, Rob Landers wrote:
> The moment we allow "namespace prefixes", we introduce questions that > need to be fleshed out separately and with care. Do we treat > "Acme\AuthLib\Session" and "Acme\AuthLib\Services" as related? Even if > they come from different vendors and are unrelated in any way? Are > namespaces hierarchical or just flat strings that happen to contain > separators?
While I appreciate the desire to keep things simple, I don't think we can avoid asking those questions, we can only propose answers. In your current RFC, the answers are: - No two namespaces are related, even if their names imply a hierarchy. - Two "internal" classes in the same namespace can see each other even if they are written by different vendors.
> Today namespaces are a name resolution mechanism, not a semantic > hierarchy. Matching by prefix would start treating them as a package > system, and I explicitly am trying to avoid that in this RFC.
I don't think that's true. The language already recognises the namespace separator, and that if you are in namespace "Acme\AuthLib", an unqualified reference to "Services\SessionManager" refers to "Acme\AuthLib\Services\SessionManager". That doesn't require any concept of "package", but it is clearly based on the conception of namespaces as a hierarchy.
> By restricting the rule to exact namespace equality, the feature is > straightforward to explain and to understand. It also prevents > "accidental" access because two unrelated namespaces happen to share a > prefix.
My concern is that by restricting it so much, we would prevent most of the real-world use cases for it, since the reality is that people use much more complex namespace hierarchies.
> If you have a concrete, unambiguous rule for prefix-based access that > wouldn’t cause surprises or make assumptions about how people organise > their codebases, I’d be happy to discuss it. Right now, every version > I’ve explored would reopen the "module"/"package" debate from this summer.
I mentioned, briefly, two possibilities:
> I think we need a keyword or attribute which takes as a parameter > either the namespace prefix, or the number of levels to match
To spell those out, the prefix version could look like this: namespace Acme\AuthLib\Somewhere\Deep\In\Package; // ... #[NamespacePrivate('Acme\AuthLib', includeChildren: true)] A number of levels version could look like this: namespace Acme\AuthLib\Somewhere\Deep\In\Package; // ... #[NamespacePrivate(minLevels: 2, maxLevels: null)] There's all sorts of variations on how those arguments could be presented, keywords vs attributes, etc; but the key point is the language is not defining where the boundary is, it is requiring the user to do so. The prefix-based approach could be expanded into an allow-list that didn't require any relationship to the current namespace at all:  namespace Acme\AuthLib\Somewhere\Deep\In\Package; // ... #[AllowNamespace('Acme\AuthLib\*')] #[AllowNamespace('Zeppo\FrameworkCore\*')] Or even an allow-deny rule system: #[AllowNamespace('Acme\AuthLib\*')] #[DenyNamespace('Acme\AuthLib\Plugins\*')] At that point, it's admittedly rather complex, but it makes *even fewer* assumptions about what constitutes a "package".
-- Rowan Tommins [IMSoP]

Rob Landers

296 days ago
On Sat, Nov 8, 2025, at 22:20, Rowan Tommins [IMSoP] wrote:
> On 08/11/2025 20:08, Rob Landers wrote: > > The moment we allow "namespace prefixes", we introduce questions that > > need to be fleshed out separately and with care. Do we treat > > "Acme\AuthLib\Session" and "Acme\AuthLib\Services" as related? Even if > > they come from different vendors and are unrelated in any way? Are > > namespaces hierarchical or just flat strings that happen to contain > > separators? > > > While I appreciate the desire to keep things simple, I don't think we > can avoid asking those questions, we can only propose answers. In your > current RFC, the answers are: > > - No two namespaces are related, even if their names imply a hierarchy. > - Two "internal" classes in the same namespace can see each other even > if they are written by different vendors. > > > > Today namespaces are a name resolution mechanism, not a semantic > > hierarchy. Matching by prefix would start treating them as a package > > system, and I explicitly am trying to avoid that in this RFC. > > I don't think that's true. The language already recognises the namespace > separator, and that if you are in namespace "Acme\AuthLib", an > unqualified reference to "Services\SessionManager" refers > to "Acme\AuthLib\Services\SessionManager". That doesn't require any > concept of "package", but it is clearly based on the conception of > namespaces as a hierarchy. > > > > By restricting the rule to exact namespace equality, the feature is > > straightforward to explain and to understand. It also prevents > > "accidental" access because two unrelated namespaces happen to share a > > prefix. > > My concern is that by restricting it so much, we would prevent most of > the real-world use cases for it, since the reality is that people use > much more complex namespace hierarchies. > > > > If you have a concrete, unambiguous rule for prefix-based access that > > wouldn’t cause surprises or make assumptions about how people organise > > their codebases, I’d be happy to discuss it. Right now, every version > > I’ve explored would reopen the "module"/"package" debate from this summer. > > I mentioned, briefly, two possibilities: > > > I think we need a keyword or attribute which takes as a parameter > > either the namespace prefix, or the number of levels to match > > To spell those out, the prefix version could look like this: > > namespace Acme\AuthLib\Somewhere\Deep\In\Package; > // ... > #[NamespacePrivate('Acme\AuthLib', includeChildren: true)] > > > A number of levels version could look like this: > > namespace Acme\AuthLib\Somewhere\Deep\In\Package; > // ... > #[NamespacePrivate(minLevels: 2, maxLevels: null)] > > There's all sorts of variations on how those arguments could be > presented, keywords vs attributes, etc; but the key point is the > language is not defining where the boundary is, it is requiring the user > to do so. > > > The prefix-based approach could be expanded into an allow-list that > didn't require any relationship to the current namespace at all: > > namespace Acme\AuthLib\Somewhere\Deep\In\Package; > // ... > #[AllowNamespace('Acme\AuthLib\*')] > #[AllowNamespace('Zeppo\FrameworkCore\*')] > > Or even an allow-deny rule system: > > #[AllowNamespace('Acme\AuthLib\*')] > #[DenyNamespace('Acme\AuthLib\Plugins\*')] > > At that point, it's admittedly rather complex, but it makes *even fewer* > assumptions about what constitutes a "package". > > > -- > Rowan Tommins > [IMSoP] >
Hi Rowan, Thanks for expanding the examples. Those approaches: prefix matching, wildcard matching, level-based matching, or allow/deny lists are all significantly more expressive than what this RFC is aiming for. They move the discussion from "namespace -private visibility" into designing a generalised access-control system or a de-facto package model. That’s a much wider problem space, and would need its own design work (and, likely, its own RFC). The goal of this RFC is intentionally minimal: take the boundary that already exists in the language (the lexical namespace) and allow private visibility to extend across it. Exact namespace equality keeps the rule unambiguous, requires no new access-control model, and avoids assumptions about how developers structure hierarchies. I agree that broader forms of scoping could be useful, but they should be in their own proposal. One advantage of keeping this RFC small is that it can be expanded later without a BC break. For example, prefix matching (maybe something like `private(namespace: Acme\AuthLib)`) or a protected-namespace variant could be layered on top if the community wants to go in that direction. This RFC isn’t intended to solve the entire space; it’s the smallest useful step that seems to fairly cleanly fit into PHP’s current model without too much churn on the engine. — Rob

Rowan Tommins [IMSoP]

296 days ago
On 08/11/2025 21:56, Rob Landers wrote:
> > Those approaches: prefix matching, wildcard matching, level-based > matching, or allow/deny lists are all significantly more expressive > than what this RFC is aiming for.
Yes. That's why I think they're a better idea.
> They move the discussion from "namespace -private visibility" into > designing a generalised access-control system or a de-facto package model.
No. They explicitly *avoid* defining any kind of package model, by letting the user mention *whatever prefix they like*. That might match the root namespace they mention in composer.json, but it might just be a particular set of classes in a giant spaghetti application with no conception of "package" whatsoever.
> The goal of this RFC is intentionally minimal: take the boundary that > already exists in the language (the lexical namespace)
From the language's point of view, "namespace Foo\Bar; $x = new Baz;" is interchangeable with "namespace Foo; $x = new Bar\Baz;" Namespaces are inherently hierarchical, and *any* cut-off point in that hierarchy is an equally natural boundary.
> Exact namespace equality keeps the rule unambiguous
What is ambiguous about any of the examples I showed?
> requires no new access-control model
I have no idea what you mean by this. We don't currently have any namespace-based access-control, and we're addding it. How does "must match exactly" vs "must match given prefix" change that?
> avoids assumptions about how developers structure hierarchies
On the contrary, it assumes that a developer will structure their hierarchy so that related classes are in the same namespace, and not any parent or child namespaces. My suggestion, on the other hand, truly makes no assumption: it lets the developer specify exactly the structure they want.
> One advantage of keeping this RFC small is that it can be expanded > later without a BC break. For example, prefix matching (maybe > something like `private(namespace: Acme\AuthLib)`) or a > protected-namespace variant could be layered on top if the community > wants to go in that direction.
That's a reasonable argument, but the risk is that if we don't consider what that future scope would look like, we can end up with syntax or semantics that makes our lives unnecessarily difficult later. As I understand it, this happened to some extent with the "readonly" keyword.
> This RFC isn’t intended to solve the entire space; it’s the smallest > useful step that seems to fairly cleanly fit into PHP’s current model > without too much churn on the engine.
I think my fundamental question is: is it actually that useful? How often, in practice, do people want exact namespace matching, vs something more flexible?
-- Rowan Tommins [IMSoP]

Larry Garfield

296 days ago
On Sat, Nov 8, 2025, at 5:13 PM, Rowan Tommins [IMSoP] wrote:
> On 08/11/2025 21:56, Rob Landers wrote:
>> One advantage of keeping this RFC small is that it can be expanded >> later without a BC break. For example, prefix matching (maybe >> something like `private(namespace: Acme\AuthLib)`) or a >> protected-namespace variant could be layered on top if the community >> wants to go in that direction. > > That's a reasonable argument, but the risk is that if we don't consider > what that future scope would look like, we can end up with syntax or > semantics that makes our lives unnecessarily difficult later. As I > understand it, this happened to some extent with the "readonly" keyword.
Exactly the example I was thinking of. :-) "Junior version first" approaches sometimes work out (FCC doesn't seem to have caused issues for PFA in practice), and other times not (readonly was a major PITA for aviz, and even delayed it for a release as we figured out how they should interact). I'd much rather know what the "full solution" looks like and plan for it, even if it's in incremental steps, than throw a junior version at the wall and hope for the best.
>> This RFC isn’t intended to solve the entire space; it’s the smallest >> useful step that seems to fairly cleanly fit into PHP’s current model >> without too much churn on the engine. > > I think my fundamental question is: is it actually that useful? How > often, in practice, do people want exact namespace matching, vs > something more flexible?
In practice, the extra-class boundary I most often want is "my composer package." I rarely need to protect things beyond that. I am broadly in favor of extra-class visibility controls, whether they go through a module system or something else. Whether the current RFC is a good way of doing so, I am not convinced. In particular, the syntax is a hard-no. It runs contrary to how the aviz syntax was defined, as others have already noted. If you don't want to use `internal(set)` to save `internal` for some other use, that's fine. Come up with a different keyword. :-) But <allowed scope>(<operation>) is the syntax model that aviz established, and tossing extra parens in there just confuses things for everyone. If it really does need to be an entirely separate dimension of scoping, then I would argue it's too complex to capture in just keywords and Rowan's attribute proposal becomes even more compelling. That said, I'm not convinced it is a separate dimension yet. I'd put "nsviz" between public and protected, not protected and private. The assumption is that your "allowed" scope is something you control. So if you don't want to use that property from child classes in your own namespace... just don't. If you don't want to allow it to be used by child classes in another namespace... then nsviz(get) solves that problem. I will also note that while it's typical for namespace and file path to be 1:1, there's nothing that requires it. That means any strictly namespace-based visibility is trivial to bypass. For example: // vendor/foo/bar/Baz.php namespace Foo\Bar\Baz; class Careful { nsviz string $secret; } // /app/User.php namespace Foo\Bar; class Invader { public function hax(Careful $c): string { return $c->secret; } } namespace App; class User { public function doStuff(Careful $c) { $secret = new Foo\Bar\Invader($c)->hax(); // Boom, we've read the value. } } Whether that is a design flaw or a feature is, I suppose, debatable. --Larry Garfield

Rob Landers

296 days ago
On Sun, Nov 9, 2025, at 16:33, Larry Garfield wrote:
> On Sat, Nov 8, 2025, at 5:13 PM, Rowan Tommins [IMSoP] wrote: > > On 08/11/2025 21:56, Rob Landers wrote: > > >> One advantage of keeping this RFC small is that it can be expanded > >> later without a BC break. For example, prefix matching (maybe > >> something like `private(namespace: Acme\AuthLib)`) or a > >> protected-namespace variant could be layered on top if the community > >> wants to go in that direction. > > > > That's a reasonable argument, but the risk is that if we don't consider > > what that future scope would look like, we can end up with syntax or > > semantics that makes our lives unnecessarily difficult later. As I > > understand it, this happened to some extent with the "readonly" keyword. > > Exactly the example I was thinking of. :-) "Junior version first" approaches sometimes work out (FCC doesn't seem to have caused issues for PFA in practice), and other times not (readonly was a major PITA for aviz, and even delayed it for a release as we figured out how they should interact). I'd much rather know what the "full solution" looks like and plan for it, even if it's in incremental steps, than throw a junior version at the wall and hope for the best. > > >> This RFC isn’t intended to solve the entire space; it’s the smallest > >> useful step that seems to fairly cleanly fit into PHP’s current model > >> without too much churn on the engine. > > > > I think my fundamental question is: is it actually that useful? How > > often, in practice, do people want exact namespace matching, vs > > something more flexible? > > In practice, the extra-class boundary I most often want is "my composer package." I rarely need to protect things beyond that. > > I am broadly in favor of extra-class visibility controls, whether they go through a module system or something else. Whether the current RFC is a good way of doing so, I am not convinced. > > In particular, the syntax is a hard-no. It runs contrary to how the aviz syntax was defined, as others have already noted. > > If you don't want to use `internal(set)` to save `internal` for some other use, that's fine. Come up with a different keyword. :-) But <allowed scope>(<operation>) is the syntax model that aviz established, and tossing extra parens in there just confuses things for everyone. > > If it really does need to be an entirely separate dimension of scoping, then I would argue it's too complex to capture in just keywords and Rowan's attribute proposal becomes even more compelling. > > That said, I'm not convinced it is a separate dimension yet. I'd put "nsviz" between public and protected, not protected and private. The assumption is that your "allowed" scope is something you control. So if you don't want to use that property from child classes in your own namespace... just don't. If you don't want to allow it to be used by child classes in another namespace... then nsviz(get) solves that problem. > > > I will also note that while it's typical for namespace and file path to be 1:1, there's nothing that requires it. That means any strictly namespace-based visibility is trivial to bypass. > > For example: > > [snip] > > Whether that is a design flaw or a feature is, I suppose, debatable. > > > --Larry Garfield
Hi Larry (and everyone else following the thread), I think we’re all aligned that PHP would benefit from a proper module or package boundary. Many features people want — including namespace visibility — become cleaner once such a boundary exists. One doesn’t strictly require the other, but they definitely complement each other. The difficulty is that PHP currently has no agreed-upon definition of what a “module” or “package” *is*: • Composer root namespaces? • Physical directories? • Compilation units? • Attributes? • Something else entirely? Because of that, there’s no existing boundary we can safely build on without first settling that much larger question. That’s why this RFC deliberately stays within a boundary the language *already* defines: the lexical namespace. It doesn’t try to define packages or introduce any new scoping model. It also doesn’t prevent a future RFC from doing so. If the community reaches consensus on a formal module/package boundary later, namespace-level visibility can coexist with it or even be folded into it. Nothing in this RFC blocks that path. What this RFC does is provide a useful, enforceable intermediate step that doesn’t require the community to solve “packages” first. If we require a full module system before adding any visibility improvements, we’re essentially saying that no incremental progress is acceptable until the hardest part of the problem is fully solved. That’s historically not how PHP evolves. To keep this discussion focused: If someone has concerns about *this RFC’s* semantics, performance, syntax, implementation, error messaging, interaction with inheritance/traits, or BC impact — that’s absolutely fair and I’d like to address them: But I’d like to avoid turning this into a thread about defining a package/specification/module system. That’s a valid discussion, and I’d happily participate in another thread, but it’s not one this RFC is trying to solve. Namespace visibility is intentionally small, intentionally conservative, and intentionally compatible with whatever direction modules eventually take. It’s the smallest useful step the language can take today, without blocking future work, while still being useful. ---- Now, to reply to your specific points about this RFC: First, syntax vs. AViz. Aviz establishes `visibility(operation)` as the pattern for asymmetric visibility, where the keyword controls the caller set and parentheses restrict the operation (get/set). That’s why `private(namespace)(set)` follows the same rule: the base visibility is still "private", and the parentheses narrows who may call it. If we introduced a standalone keyword like `internal` or `nsviz`, we’d effectively be adding a new visibility class, not a refinement of `private` and would bring its own semantics, collision issues, and interactions with any future module work. This RFC aims to minimise surface area, which is why it treats namespace visibility as a refinement. That said, if the consensus emerges around a new keyword, that’s something I can adjust. The important behaviour is the caller rule, not the exact token spelling. Second, is exact-match useful enough? Yes, there are plenty of codebases where internal helpers live side-by-side in one namespace, especially in domain-driven or layered architectures where namespaces are the boundaries. Today, teams either make them public, wrap them in a service class, use `@internal`, or wire them together via reflection or debug_backtrace. All of those come with tradeoffs. Exact-match namespace visibility offers a simple enforceable boundary without designing a whole package system or assuming anything about code hierarchy. It’s not the most expressive possible rule, but it’s useful and predictable. If the community prefers prefix-based visibility or package-level visibility, that could be explored in a follow-up RFC. I’m not opposed to more expressive forms; I’m just not binding this RFC to a package model the language hasn’t defined yet. And lastly, isn’t namespace visibility easy to bypass? Yes — but so is private and protected. No PHP visibility is intended as a security boundary. This is a developer-intent boundary: encapsulation, static analysis, and making accidental misuse harder. If a future module/package system introduces stronger enforcement, this RFC can layer underneath it without a conflict. Sincerely, — Rob

Tim Düsterhus

295 days ago
Hi On 11/9/25 18:20, Rob Landers wrote:
> What this RFC does is provide a useful, enforceable intermediate step that doesn’t require the community to solve “packages” first. If we require a full module system before adding any visibility improvements, we’re essentially saying that no incremental progress is acceptable until the hardest part of the problem is fully solved. That’s historically not how PHP evolves. > […] > Namespace visibility is intentionally small, intentionally conservative, and intentionally compatible with whatever direction modules eventually take. It’s the smallest useful step the language can take today, without blocking future work, while still being useful.
As I believe I had previously said before, I'm in favor of small RFCs and features *that compose well*. I am therefore generally sympathetic towards making small incremental improvements with a bigger picture in mind. Features that compose well make the language easier to learn and understand. This was an important part of the decision why Volker and I ultimately decided to use an array parameter for clone-with. And also why it was important to me that the pipe operator and partial-function application are independently usable feature For this one I am however not sure if it ticks the “composes well” checkbox - that greatly depends on the syntax choice and how modules will look like if/when they eventually arrive. I also believe that the current usefulness is debatable, especially when considering how folks are already structuring their applications as of now (as indicated by Rowan) and when also considering the ecosystem impact. Your RFC appears to use the old template for the “RFC Impact” section which doesn't yet include the “Ecosystem Impact” subsection, but indicating that “significant OPcache changes” are required makes me wonder about the cost-benefit ratio.
> Aviz establishes `visibility(operation)` as the pattern for asymmetric visibility, where the keyword controls the caller set and parentheses restrict the operation (get/set). That’s why `private(namespace)(set)` follows the same rule: the base visibility is still "private", and the parentheses narrows who may call it. > > If we introduced a standalone keyword like `internal` or `nsviz`, we’d effectively be adding a new visibility class, not a refinement of `private` and would bring its own semantics, collision issues, and interactions with any future module work. This RFC aims to minimise surface area, which is why it treats namespace visibility as a refinement.
As noted in my reply in the thread from Faizan, calling this a refinement of `private` is not really accurate / doesn't work in practice.
> If the community prefers prefix-based visibility or package-level visibility, that could be explored in a follow-up RFC. I’m not opposed to more expressive forms; I’m just not binding this RFC to a package model the language hasn’t defined yet.
To do so, the syntax would need to account for that. I have not yet seen a good proposal for that that doesn't end up as “symbol soup” that doesn't really fit the existing language syntactically.
> And lastly, isn’t namespace visibility easy to bypass? > > Yes — but so is private and protected. No PHP visibility is intended as a security boundary. This is a developer-intent boundary: encapsulation, static analysis, and making accidental misuse harder.
I fully agree with this part. It's important that bypassing these type of control mechanisms is a fairly explicit act that stands out in review, but it's not important to lock it down 100% - unless there are good (engine-related) reasons to do so. Best regards Tim Düsterhus

Rob Landers

295 days ago
On Sun, Nov 9, 2025, at 21:51, Tim Düsterhus wrote:
> For this one I am however not sure if it ticks the “composes well” > checkbox - that greatly depends on the syntax choice and how modules > will look like if/when they eventually arrive.
I understand the concern. Composability matters a lot, especially for features that touch visibility. My goal with this RFC is to take a boundary PHP already has (the lexical namespace) and make it enforceable without needing to answer the bigger "what’s a module/package?" question first. Right now, different people in the ecosystem use namespaces in different ways: some treat them as hierarchical, some as flat prefixes, some map them to directory trees, some don’t. Trying to define prefix rules, upward/downward access, or package-like confinement gets us right back into the same conversation we’ve been stuck on. That’s why this RFC deliberately picks the simplest rule PHP could enforce today: exact namespace equality. If a future RFC defines modules/packages, namespace-visibility can either: - fold into that boundary, - be superseded by it, or - be used inside it (e.g. `internal` for modules, `private(namespace)` within module internals). Nothing in this RFC makes that harder.
> > Your RFC appears to use the old template for the “RFC Impact” section > which doesn't yet include the “Ecosystem Impact” subsection, but > indicating that “significant OPcache changes” are required makes me > wonder about the cost-benefit ratio.
Thanks! I’ll look at the new template and call out ecosystem impact (this was originally written back in April/May?). On the OPcache point: "significant" is probably overstating it. The change is limited to persisting one additional interned string on zend_op_array and refcounting it correctly. The cost is paid at compile time, not at call time, so runtime performance impact should be negligible. I’ll reword this to be more precise.
> > > Aviz establishes `visibility(operation)` as the pattern for asymmetric visibility, where the keyword controls the caller set and parentheses restrict the operation (get/set). That’s why `private(namespace)(set)` follows the same rule: the base visibility is still "private", and the parentheses narrows who may call it. > > > > If we introduced a standalone keyword like `internal` or `nsviz`, we’d effectively be adding a new visibility class, not a refinement of `private` and would bring its own semantics, collision issues, and interactions with any future module work. This RFC aims to minimise surface area, which is why it treats namespace visibility as a refinement. > > As noted in my reply in the thread from Faizan, calling this a > refinement of `private` is not really accurate / doesn't work in practice.
Agreed. After the discussion with you, Alex, and Larray, I think it's clearer to describe `private(namespace)` as a distinct caller-set, not a subset of protected or private. I’ll update the RFC text to reflect that and disallow weird combinations (to be more clearly defined in the RFC).
> > > If the community prefers prefix-based visibility or package-level visibility, that could be explored in a follow-up RFC. I’m not opposed to more expressive forms; I’m just not binding this RFC to a package model the language hasn’t defined yet. > > To do so, the syntax would need to account for that. I have not yet seen > a good proposal for that that doesn't end up as “symbol soup” that > doesn't really fit the existing language syntactically.
My earliest version simply used `namespace`: class P { namespace function x() {} } It might make sense to return to that syntax if people don’t like the current syntax. I don’t have a strong attachment to the exact spelling, what matters is the semantics. — Rob

Tim Düsterhus

295 days ago
Hi On 11/9/25 16:33, Larry Garfield wrote:
> In particular, the syntax is a hard-no. It runs contrary to how the aviz syntax was defined, as others have already noted. > > If you don't want to use `internal(set)` to save `internal` for some other use, that's fine. Come up with a different keyword. :-) But <allowed scope>(<operation>) is the syntax model that aviz established, and tossing extra parens in there just confuses things for everyone.
I agree with that.
> If it really does need to be an entirely separate dimension of scoping, then I would argue it's too complex to capture in just keywords and Rowan's attribute proposal becomes even more compelling.
I disagree that this should be an Attribute for the inverse of the reasons of why `#[\Override]` *is* an attribute: https://wiki.php.net/rfc/marking_overriden_methods#why_an_attribute_and_not_a_keyword Visibility is part of the public API and by making this an attribute, the code will seemingly appear to work in older PHP version that do not yet know about the attribute, but its behavior will differ. It would of course also be syntactically / visually inconsistent with any other visibility definition. Best regards Tim Düsterhus

Derick Rethans

294 days ago
On Sat, 8 Nov 2025, Rowan Tommins [IMSoP] wrote:
> To spell those out, the prefix version could look like this: > > namespace Acme\AuthLib\Somewhere\Deep\In\Package; > // ... > #[NamespacePrivate('Acme\AuthLib', includeChildren: true)]
Whatever the way to define a prefix is going to be, I disagree with that being done through attributes. Nowhere in the language do these annotations (yet) interfere with how code can be run. cheers, Derick

Rowan Tommins [IMSoP]

294 days ago
On 11/11/2025 11:36, Derick Rethans wrote:
> On Sat, 8 Nov 2025, Rowan Tommins [IMSoP] wrote: > >> To spell those out, the prefix version could look like this: >> >> namespace Acme\AuthLib\Somewhere\Deep\In\Package; >> // ... >> #[NamespacePrivate('Acme\AuthLib', includeChildren: true)] > Whatever the way to define a prefix is going to be, I disagree with that > being done through attributes. > > Nowhere in the language do these annotations (yet) interfere with > how code can be run.
The main reason my mind went in that direction is that people are building attributes for this in userspace already - set the actual visibility to public, and then use an attribute for static analysis to restrict it. They also have a standard syntax for arguments, so it's easy to illustrate those without spending time imagining a new syntax. But in general, I agree, a native implementation would ideally look and feel like it matched the existing "private" keyword.
-- Rowan Tommins [IMSoP]

Faizan Akram Dar

297 days ago
Hi, I like the proposal however the choice of syntax seems inconsistent. Traditionally visibility is defined by a single keyword like private, protected, etc, each implicitly defining the scope from which the property is accessible. Aviz (PHP 8.4) added the possibility of defining the operations that can be performed in the given scope like (get) or (set), the scope however is still defined by the existing keywords (private for class scope and protected for child scope). As such, I'd suggest to use a dedicated keyword for namespace level visibility like "internal" or "ns-private", the operations will still be defined within braces (). An excerpt from future scope of aviz rfc differentiating between visibility and operations.
> At this time, there are only two possible operations to scope: read and
write. In concept, additional operations could be added with their own visibility controls. Possible examples include: protected(&get) - Vary whether a reference to a property can be obtained independently of getting the value. (Would override the set visibility if used.) private(setref) - Allows a property to be set by reference only from certain scopes. [image: --] Faizan Akram Dar [image: https://]faizanakram.me <http://faizanakram.me> On Sat, 8 Nov 2025, 14:21 Rob Landers, <rob@bottled.codes> wrote:

Rob Landers

297 days ago
On Sat, Nov 8, 2025, at 18:43, Faizan Akram Dar wrote:
> Hi, > > I like the proposal however the choice of syntax seems inconsistent. > > Traditionally visibility is defined by a single keyword like private, protected, etc, each implicitly defining the scope from which the property is accessible. > > Aviz (PHP 8.4) added the possibility of defining the operations that can be performed in the given scope like (get) or (set), the scope however is still defined by the existing keywords (private for class scope and protected for child scope). > As such, I'd suggest to use a dedicated keyword for namespace level visibility like "internal" or "ns-private", the operations will still be defined within braces (). > > > An excerpt from future scope of aviz rfc differentiating between visibility and operations. > > > At this time, there are only two possible operations to scope: read and write. In concept, additional operations could be added with their own visibility controls. Possible examples include: > protected(&get) - Vary whether a reference to a property can be obtained independently of getting the value. (Would override the set visibility if used.) > private(setref) - Allows a property to be set by reference only from certain scopes. > > > -- > > > Faizan Akram Dar > https://faizanakram.me > > > On Sat, 8 Nov 2025, 14:21 Rob Landers, <rob@bottled.codes> wrote: >> __ >> Hello Internals, >> >> I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). >> >> This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. >> >> Sincerely, >> >> Rob Landers
Please remember to bottom post! The reason I chose private(namespace) rather than a new keyword is that the semantics are fundamentally a widened form of private. Access is granted to code in the exact same namespace and nowhere else. There's no additional scope concept beyond that. Introducing a new keyword like internal or ns-private suggests a new typeof boundary rather than a refinement on existing ones. In other languages, internal et al. is tied to a module, assembly, crate, and not a namespace. PHP doesn’t have a formal module boundary today, and adopting the term internal would imply we’re defining one. Sidenote: When I proposed nested classes earlier this year, the conversation quickly shifted into what "packages" or "modules" should mean for PHP, and that ended up becoming a much larger debate. I’d like to avoid pulling that discussion into this RFC. It’s a related, but orthogonal topic and could absolutely be explored in a *separate thread.* private(namespace) follows the same syntactic pattern introduced in asymmetric visibility: a base keyword with a parenthesised refinement. In AViz, the refinement controls operations (set/get) and here it refines which callers are allowed. The syntax feels familiar and doesn’t require introducing a new keyword. If we end up introducing modules/packages in a future RFC, a dedicated keyword like internal could absolutely build on top of this. For now, the goal of this RFC is to make a small, well-defined improvement that can be improved upon in future RFCs. — Rob

Deleu

296 days ago
On Sat, 8 Nov 2025 at 17:18 Rob Landers <rob@bottled.codes> wrote:
> > > Sidenote: > When I proposed nested classes earlier this year, the conversation quickly > shifted into what "packages" or "modules" should mean for PHP, and that > ended up becoming a much larger debate. I’d like to avoid pulling that > discussion into this RFC. It’s a related, but orthogonal topic and could > absolutely be explored in a *separate thread.* > > — Rob >
I understand the sentiment. I’ve participated in some of these debates. It seems like reaching consensus is impossible because it’s not possible for a solution to be perfect and there’s no consensus in choosing the least worst option. Unfortunately, namespace visibility, nested classes, class friendship and even some long debated performance optimizations which may or may not allow linking symbols and may or may not help Generics all sit around on finding a way to go from namespaces to packages. This alternative, to me, seems more likely to make things worse rather than better and I really hope PHP can figure out a package system.

Tim Düsterhus

296 days ago
Hi On 11/8/25 21:09, Rob Landers wrote:
> Please remember to bottom post!
And please also remember to cut the quotes to the relevant part instead of just full quoting.
> The reason I chose private(namespace) rather than a new keyword is that the semantics are fundamentally a widened form of private.
This is not accurate. As Alexandru also noted, it rather is an alternative to `protected`. As I believe I had also noted for the nested classes RFC, `private` is special in that it does affect any other class at all. For all intents and purposes it is not visible to the outside and new private symbols can be introduced without taking care of possible parent or child classes. From what I understand this is different for namespace-visibility as suggested in the RFC. Since the method is externally visible, changes to the signature affect both external callers and child classes, which need to maintain a compatible signature. In fact it has a rather odd effect on child classes in a different namespace in that some methods are just unavailable to them. It is probably necessarily to restrict inheriting from classes with namespace-restricted symbols to classes from the same namespace (i.e. making the class itself namespace-final so to say). Best regards Tim Düsterhus

Alexandru Pătrănescu

296 days ago
Hi Rob On Sat, Nov 8, 2025, 14:46 Rob Landers <rob@bottled.codes> wrote:
> Hello Internals, > > I’d like to introduce an RFC for discussion: > https://wiki.php.net/rfc/namespace_visibility which proposes a new > visibility modifier: private(namespace). > > This idea has appeared several times in previous threads but never > progressed to a formal proposal (from what I could find). My hope is that > with defined semantics, examples, and implementation details, we can > evaluate it properly and see whether there’s support for moving forward. > Feedback is very welcome. > >
Nice work on this. I have one issue:
> *Visibility hierarchy: *public < protected < private(namespace) < private
I think is not a correct view of the real problem space, as the protected and private namespace scopes are separate sets that might have things in common but can also be distinct. I think the correct way to model it, is to have two hierarchies: public < protected < private public < private(namespace) < private Otherwise you can have things like `protected private(namespace)(set)` that is unclear how it should be handled. Can you clarify what is the right-now expected get and set allowance for these cases: - child classes in the same namespace - child classes in another namespace - non-child classes in the same namespace My suggestion is to not allow mixing protected and private namespace for aviz.
-- Alex

Rob Landers

296 days ago
On Sun, Nov 9, 2025, at 07:09, Alexandru Pătrănescu wrote:
> Hi Rob > > On Sat, Nov 8, 2025, 14:46 Rob Landers <rob@bottled.codes> wrote: >> __ >> Hello Internals, >> >> I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). >> >> This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. >> >> > > Nice work on this. > > > I have one issue: > > *Visibility hierarchy: *public < protected < private(namespace) < private > >> > I think is not a correct view of the real problem space, as the protected and private namespace scopes are separate sets that might have things in common but can also be distinct. > > I think the correct way to model it, is to have two hierarchies: > public < protected < private > public < private(namespace) < private > > Otherwise you can have things like > `protected private(namespace)(set)` > that is unclear how it should be handled. > Can you clarify what is the right-now expected get and set allowance for these cases: > - child classes in the same namespace > - child classes in another namespace > - non-child classes in the same namespace > > > My suggestion is to not allow mixing protected and private namespace for aviz. > > -- > Alex
Hi Alex, I think you’re right, treating this as a simple linear hierarchy is misleading. `Protected` and `private(namespace)` *are *based on different axes: - `protected` is inheritance-based - `private(namespace)` is namespace-based So, for `protected private(namespace)`: - child class in the same namespace: read + write - child class in a different namespace: read-only - non-child class in the same namespace: forbidden Formally, we can consider the caller sets: - C[public] - C[protected] (declaring class ∪ subclasses) - C[ns] (all code in the exact declaring namespace) - C[private] (declaring class only) We have two partial orders: - C[public] ⊇ C[protected] ⊇ C[private] - C[public] ⊇ C[ns] ⊇ C[private] In general, C[protected] and C[ns] are incomparable (neither is a subset of the other). For asymmetric properties, the `(set)` visibility must satisfy C[set] ⊇ C[base]. If C[set] and C[base] are incomparable or otherwise not a subset, it’s a compile-time error. That yields: - `public` with any `(set)` visibility: C[any] ⊆ C[public] - `protected` with `protected(set)` or `private(set)` only: C[private] ⊆ C[protected] - `private(namespace)` with `private(namespace)(set)` or `private(set)` only: C[private] ⊆ C[ns] - `private` with `private(set)` only: C[private] ⊆ C[private] - `protected` with `private(namespace)(set)`: incomparable - `private` with `private(namespace)(set)`: C[ns] ⊈ C[private] I’ll update the RFC to drop the linear hierarchy and update with the subset rule explicitly with some examples. — Rob

Rob Landers

296 days ago
On Sun, Nov 9, 2025, at 10:45, Rob Landers wrote:
> > > On Sun, Nov 9, 2025, at 07:09, Alexandru Pătrănescu wrote: >> Hi Rob >> >> On Sat, Nov 8, 2025, 14:46 Rob Landers <rob@bottled.codes> wrote: >>> __ >>> Hello Internals, >>> >>> I’d like to introduce an RFC for discussion: https://wiki.php.net/rfc/namespace_visibility which proposes a new visibility modifier: private(namespace). >>> >>> This idea has appeared several times in previous threads but never progressed to a formal proposal (from what I could find). My hope is that with defined semantics, examples, and implementation details, we can evaluate it properly and see whether there’s support for moving forward. Feedback is very welcome. >>> >>> >> >> Nice work on this. >> >> >> I have one issue: >> > *Visibility hierarchy: *public < protected < private(namespace) < private >> >>> >> I think is not a correct view of the real problem space, as the protected and private namespace scopes are separate sets that might have things in common but can also be distinct. >> >> I think the correct way to model it, is to have two hierarchies: >> public < protected < private >> public < private(namespace) < private >> >> Otherwise you can have things like >> `protected private(namespace)(set)` >> that is unclear how it should be handled. >> Can you clarify what is the right-now expected get and set allowance for these cases: >> - child classes in the same namespace >> - child classes in another namespace >> - non-child classes in the same namespace >> >> >> My suggestion is to not allow mixing protected and private namespace for aviz. >> >> -- >> Alex > > Hi Alex, > > I think you’re right, treating this as a simple linear hierarchy is misleading. `Protected` and `private(namespace)` *are *based on different axes: > > - `protected` is inheritance-based > - `private(namespace)` is namespace-based > > So, for `protected private(namespace)`: > - child class in the same namespace: read + write > - child class in a different namespace: read-only > - non-child class in the same namespace: forbidden > > Formally, we can consider the caller sets: > > - C[public] > - C[protected] (declaring class ∪ subclasses) > - C[ns] (all code in the exact declaring namespace) > - C[private] (declaring class only) > > We have two partial orders: > - C[public] ⊇ C[protected] ⊇ C[private] > - C[public] ⊇ C[ns] ⊇ C[private] > > In general, C[protected] and C[ns] are incomparable (neither is a subset of the other). > > For asymmetric properties, the `(set)` visibility must satisfy C[set] ⊇ C[base]. If C[set] and C[base] are incomparable or otherwise not a subset, it’s a compile-time error. > > That yields: > > - `public` with any `(set)` visibility: C[any] ⊆ C[public] > - `protected` with `protected(set)` or `private(set)` only: C[private] ⊆ C[protected] > - `private(namespace)` with `private(namespace)(set)` or `private(set)` only: C[private] ⊆ C[ns] > - `private` with `private(set)` only: C[private] ⊆ C[private] > - `protected` with `private(namespace)(set)`: incomparable > - `private` with `private(namespace)(set)`: C[ns] ⊈ C[private] > > I’ll update the RFC to drop the linear hierarchy and update with the subset rule explicitly with some examples. > > — Rob
I’ve updated the RFC and implementation accordingly along with some editorial changes. — Rob

Tim Düsterhus

296 days ago
Hi On 11/9/25 16:07, Rob Landers wrote:
> I’ve updated the RFC and implementation accordingly along with some editorial changes.
The RFC now mentions:
> Combinations that mix protected and private with private(namespace)(set) are a compile-time error because their allowed caller sets aren’t subsets of each other.
Which is not particularly explicit in what “mix” means. I initially missed the `(set)` in the `private(namespace)(set)` until I read the ML discussion. From what I see the RFC does not discuss the following at all: class P { protected function x() { } } class C extends P { private(namespace) function x() { } } and vice versa: class P { private(namespace) function x() { } } class C extends P { protected function x() { } } Best regards Tim Düsterhus

Rob Landers

296 days ago
On Sun, Nov 9, 2025, at 19:01, Tim Düsterhus wrote:
> Hi > > On 11/9/25 16:07, Rob Landers wrote: > > I’ve updated the RFC and implementation accordingly along with some editorial changes. > > From what I see the RFC does not discuss the following at all: > > class P { > protected function x() { } > } > class C extends P { > private(namespace) function x() { } > } > > and vice versa: > > class P { > private(namespace) function x() { } > } > class C extends P { > protected function x() { } > } > > Best regards > Tim Düsterhus
Good catch! The RFC should spell out these cases directly. The behaviour follows the same rule PHP already applies to private during inheritance: class P { protected function x() {} } class C extends P { private(namespace) function x() {} } Reducing visibility is an error. This is rejected for the same reason that redefining a protected method as private is rejected today: `C::x()` would be less visible than `P::x()`. `private(namespace)` doesn’t introduce any new ambiguity here. The name even implies that visibility will be reduced and lives below protected in the caller set, so reducing visibility is not allowed. class P { private(namespace) function x() {} } class C extends P { protected function x() {} } This behaves the same as overriding a private method with a protected/public one today: the parent’s method is private to its declaring class, so the second example is allowed. I’ll update the RFC to explicitly document both of these cases so the inheritance rules are unambiguous. Thanks for pointing it out. — Rob

Tim Düsterhus

296 days ago
Hi On 11/9/25 20:41, Rob Landers wrote:
> class P { > private(namespace) function x() {} > } > > class C extends P { > protected function x() {} > } > > This behaves the same as overriding a private method with a protected/public one today: the parent’s method is private to its declaring class, so the second example is allowed.
This is unsound. As we have established, neither `private(namespace)` nor `protected` is a subset of each other. Specifically allowing this breaks the following (everything is declared in the same namespace): class P { private(namespace) function x() { } } class C extends P { protected function x() { } } function f(P $p) { $p->x(); // legal, because f is in the same namespace as P. } f(new C()); // breaks, because C::x() is protected and thus not legal to access from f / the global scope. Best regards Tim Düsterhus

Volodymyr Volynets

295 days ago
Hi, Did we take into account prefixed namespaces. private(namespace \Users\Auth\*) function test() {} And then Auth folder has other classes and folders with child namespaces. Sent from my iPhone

Rob Landers

295 days ago
On Sun, Nov 9, 2025, at 20:55, Tim Düsterhus wrote:
> Hi > > On 11/9/25 20:41, Rob Landers wrote: > > class P { > > private(namespace) function x() {} > > } > > > > class C extends P { > > protected function x() {} > > } > > > > This behaves the same as overriding a private method with a protected/public one today: the parent’s method is private to its declaring class, so the second example is allowed. > > This is unsound. As we have established, neither `private(namespace)` > nor `protected` is a subset of each other. > > Specifically allowing this breaks the following (everything is declared > in the same namespace): > > class P { > private(namespace) function x() { } > } > class C extends P { > protected function x() { } > } > > function f(P $p) { > $p->x(); // legal, because f is in the same namespace as P. > } > > f(new C()); // breaks, because C::x() is protected and thus not > legal to access from f / the global scope. > > Best regards > Tim Düsterhus
Initially, I treated `private(namespace)` like `private` for cross-namespace inheritance and overlooked how dynamic dispatch actually works in PHP. This means that a same-named method in a child would simply be a new method. But like you point out, that leads to a subtle problem. Even though `P::x()` is a method that’s visible and intended for callers inside namespace A, the runtime dispatch would pick `C::x()`. That completely and utterly destroys substitutability for namespace internal callers, which is exactly what this visibility is meant to protect. So ... it seems we need a couple of additional rules here: 1. If a parent has a `private(namespace)` method, then a subclass in a different namespace simply cannot declare a method with the exact same name. It’d be a compile time error to prevent shadowing and keep dispatch predictable. I’d also be open to other ways as well (such as always calling the namespaced method in the parent), but I’d need to see if that’s even possible in the engine. 2. As you pointed out, `protected `and` private(namespace)` aren’t compatible; thus we should only allow the same or a superset. So, it should only allow `public` or `private(namespace)` when inheriting in the same namespace. — Rob