Re: RFC: Namespace-Scoped Visibility for Methods and Properties
| From: | Rob Landers | Date: | Tue, 11 Nov 2025 17:52:27 +0000 |
| Subject: | Re: RFC: Namespace-Scoped Visibility for Methods and Properties | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129200@lists.php.net to get a copy of this message | ||
On Mon, Nov 10, 2025, at 20:19, Rob Landers wrote:
>
>
> On Sun, Nov 9, 2025, at 22:32, Rob Landers wrote:
>>
>>
>> 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
>
> I’ve updated the RFC and the implementation with some significant clarifications and
> corrections:
> • *Inheritance semantics now follow protected rather than
> private*
> private(namespace) members are inherited and must follow normal
> signature-compatibility rules.
> Visibility is enforced based on the declaring namespace rather than the inheritance hierarchy.
> • *Incompatible redeclarations are now clearly defined*
> Transitions between protected and
> private(namespace) are disallowed in either direction.
> This avoids unsound cases where substitutability would be broken for callers in the declaring
> namespace.
> • *Asymmetric visibility rules clarified*
> protected and
> private(namespace) operate on different axes (inheritance vs
> namespace), so mixed AViz like
> protected private(namespace)(set) is now a compile-time error.
> • *Expanded examples and error messages*
> The RFC now includes clearer examples of the invalid cases, inheritance rules, and AViz
> combinations.
> • *Syntax moved to an explicit open issue*
> Because the semantics now line up with protected rather than
> private, the spelling
> private(namespace) may not be ideal.
> I’ve listed this in the “Open Issues” section and I'll include some previously
> considered alternatives that preserve the semantics here:
> • namespace function x() {}
> • local function x() {}
> • private:ns function x() {}
> • protected:ns function x() {}
> My personal preference is toward the simpler forms (namespace
> or local), but I’d like to collect feedback before changing the
> RFC text.
>
> Updated RFC:
> https://wiki.php.net/rfc/namespace_visibility
> <https://wiki.php.net/rfc/namespace_visibility?utm_source=chatgpt.com>
>
> Implementation:
> https://github.com/php/php-src/pull/20421
>
> Thanks to everyone who pointed out the inheritance edge cases; those surfaced issues that
> needed to be addressed. Further feedback is welcome.
>
> — Rob
On my commute, I was exploring the syntax further and the feedback I've gotten so far.
Specifically:
class A {
local function x() {}
}
This is accssible only with the exact same namespace where it is declared (this RFC). I was reminded
that this is very similar to "file private" but allows the boundary to extend across
multiple files in the same namespace.
Then, we could have this in a followup RFC (which I've already started drafting) that
doesn't actually require local but provides a broader "package-like"
scope without requiring PHP to define what a package is.
class A {
namespace function x() {}
}
This would be accessible within the namespace it is declared, as well as parent and child namespaces
(thus we don't make any assumptions about hierarchy).
Using the following namespace structure:
App\
├── Auth\
│ ├── SessionManager (declares namespace function validateToken())
│ ├── OAuth\
│ │ └── OAuthProvider
│ └── Session\
│ └── SessionStore
├── Billing\
│ └── PaymentProcessor
└── Controllers\
└── LoginController
The following can access validateToken() :
- App\Auth\* (So, SessionStore/OAuthProvider)
- App\* (parent namespace)
But the following cannot access it:
- App\Billing\*
- App\Controllers\*
- \ (global namespace)
The global namespace is a special case where namespace and local behave
exactly the same.
Before rewriting the RFC around this, I'd like to guage whether people feel like the keyword
based approach is a clearer direction than private(namespace).
— Rob