Re: RFC: Namespace-Scoped Visibility for Methods and Properties

From: 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

« previous php.internals (#129200) next »