Le 16/06/2026 à 06:45, Michael Morris a écrit :

On Mon, Jun 15, 2026 at 5:45 AM Alex Rock <[email protected]> wrote:

    Namespaces being just a globally accessible and define-able symbols
    system is exactly why, so far, a PHP package cannot define "private
    internal" code. The way the language works already allows
    autoload-hijacking in order to override code from external libs,
    or even
    hack the visibility (like removing "final" or "private" keywords
    before
    autoloading the file...).


PHP has never kept people from shooting themselves in the foot. And in the past it's came with some big guns too (Register Globals in PHP 3 anyone?). Enforcing namespace visibility will come at a cost in performance, and it won't be enough to stop people from shooting themselves because PHP code is quite mutable. I don't see much of a point in trying to guide people's coding, especially in a language that doesn't have a set paradigm.  That is, in Java, you're using classes. In PHP you can approach things that way, but you write purely procedurally. You can use the language as it was originally conceived - a templating language.

I think it's morally good to bring new features to the core that disallow someone from shooting themselves in the foot. Readonly classes, property hooks, etc., are methods for maintainers to be able to stricten their code so that end users don't shoot themselves in the foot. Yet we have the Reflection API that permits a huge lot of things, and combining with autoload hacking or bounded closures, it means that all these requirements, strictening tools, etc., are *contracts* we sign with maintainers. If someone uses any hacky tool built in PHP to override its native behavior, it's at their own risk. But the language allows it.

With my suggestion for modules, having all structures being declared globally but with a prefix still allows someone to hack into it. It just lowers the pressure on how it's developed in the core, in order for it to be both simple to use, but also simpler to implement. Doesn't prevent feet shots, but has a big warning sign nonetheless.


When you say modules, what are talking about, because multiple languages implement "modules" in different ways. JavaScript is the most well known, but Go also has modules and they are quite different and support versioning out of the box. In each of these examples there is a requirement that the programmer write code to import the modules at some place either in the project (Go) or in every bloody file in the project (JavaScript). PHP doesn't require this, instead autoloading symbol definitions as they are encountered.

There are different terms available for the things we talk about, so as a reminder to the two/three people reading this message:

 * Module = file, similar to JS. This implies that everything defined
   in the file is private, unless "publicly exposed", either via a
   "public" keyword (as Larry suggests in his modules-brainstorming
   document), or an "export" statement (similar to JS/TS).
 * Module = package, in that case, this means that a module is composed
   of several files, but a module/package needs an entrypoint to define
   its components. Larry's aforementioned idea is about using an INI
   file as entrypoint that prevents double-loading of a list of files,
   but this could be implemented with other methods: my proposal for
   "module = file" could add a "package" system without a need for an
   INI file, and based purely on the list of publicly available
   structures from the main imported file, but implies a bidirectional
   relationship between the "main package file" (aka "entrypoint") and
   the "package-included-only file". It's just a different approach.

None of these approaches removes namespaces, and considering my first idea and re-thinking it after all the comments in here, I think focusing on "using namespaces for that" is definitely the best approach. My proposal still stands, it just needs an update to better use namespaces for that need.


NPM (not Node, node is the engine not the package/dependency manager) does this using aliases. PHP doesn't have a way to alias symbols at compile time. It would be nice if it did - I've suggested it as a parameter to include not knowing at the time of the suggestion that include isn't a function, it's a statement, so `include "file.php"` is the same as `include("file.php")`. Setting that aside, PHP projects rarely execute the include themselves - they let an autoloader handle it, so the autoloader must know which version to load. This isn't something that even NPM does - it just aliases and makes both libraries available. The caller still must specify which library is being pulled in.

Node modules have a single point of entry, defined in the package.json file but usually index.js. In most Node packages this index.js imports all the public parts of the package and immediately exports them. Further, an import in node is an object (as is everything in JavaScript) that corresponds to that file.  Hence

import { foo, bar } from 'MyPackage';

This is using JavaScripts dereference mechanic to pluck members of an object into the current symbol table.  I could go on, but I think it's more on topic to just say that Node's import/export mechanic, elegant as it looks, is highly bound to JavaScript's scope rules and  how its symbol table works.  These aren't compatible with PHP.

I'm a bit familiar with Node.js's import model. PHP could be partly compatible with this in the future, but complete scoped encapsulation of a single PHP file is too much of a paradigm change for PHP itself. My proposal is making this "private" by prepending a null character and hash to all "included modules" whatever their kind, so that it /feels/ private, but still behaves similarly to how PHP currently works. Less maintenance for the core team, zero BC break (purely additive and opt-in), can be transparent for the end-user (and *is* transparent for transient dependencies), still overridable and hackable by developers. Some of the reasons why PHP became famous in the first place.



For one, PHP has two symbol tables - one for variables (which is why all variables start with $ ) and one for everything else. Variables are affected by function scope, nothing else is. For example, if you define a function within a function in JavaScript that function will be private to the function it is defined in - it can't be seen in other scopes. In PHP the function will be visible on the symbol table.

For another, Node gives each file its own module scope. To move a symbol from one file to another you should use export and import. This creates a clear chain of custody for objects and symbols, but by God it also creates a LOT of boilerplate that the PHP community has no interest in replicating.

Without the chain of custody you rapidly run into trouble. It doesn't matter if you try to allow the user to put a prefix on the namespace as I once suggested, or have PHP's engine prefix it automagically somehow - you still need to correctly disambiguate every blasted reference to that namespace. This is no easy task - take a look at the code of Strauss - https://github.com/BrianHenryIE/strauss That project allows you to rewrite a package with a new namespace, in effect doing it in userland. But as a precaution it prefixes every single package imported and severs the tie back to the original packagist.  Strauss is used by several WordPress plugins to make composer library use possible while still remaining portable between WordPress sites.

Interesting, and auto-prefixing namespaces for module=packages systems is IMO the easiest way to implement this without breaking the compiler nor the engine. It keeps everything as-is, and visually provides all that's needed for a "modules/packages" system to work: encapsulated private structures, an explicit public API, and the ability to load the same structures in the same namespaces because they are internally hidden with a hash, similarly to anonymous classes.




    The only drawback of having modules like that would be that final
    dependency trees in Composer would be non-flat, but again, I think
    this
    non-flat system should be opt-in. Similarly to how flat systems are
    opt-in in the Node.js ecosystem (and trust me, I've tried to enforce
    "flat" dependencies resolutions in certain Node projects, and it's a
    huge hassle). Luckily, the PHP ecosystem is quite fine so far,
    compared
    to Node, and I would definitely keep the "flat" system in Composer
    for
    as long as possible, and make non-flat resolutions only case-specific
    (which is rare, and usually framework-dependent, like with Wordpress
    plugins for instance).


I don't think it's a reasonable ask of Composer or any other dependency manager to try to resolve this without having a clear binding map like the one JavaScript forces you to create by how its module system works.

Composer doesn't have to change, because modules might be autoloadable. Whatever the solution found: if it's a bidirectional relationship (meaning any included file contains information on the "package" it's in, guiding to the entrypoint) or with a config file (like with an INI file), it can be built-in, and Composer would be compatible as long as loaded structures are public and not internal to the module.


This is the Container thread - I changed the subject line on my previous reply although I did include (was: ) so that people could track I was forking.

I don't have much to add beyond my previous post. I haven't heard from anyone with engine experience about the feasibility or difficulty of what I propose.  To review (for anyone just joining but missing the previous message)

1. The user signals to the engine they want to include a php file AND process it independent of the current execution environment. No shared user defined symbols or variables. 2. That file returns an interface so that the calling thread can talk to the child. 3. As the processes reside on different threads these calls will need to be asynchronous to be optimal, otherwise the caller will be in a blocked state while the child executes the called method. It can work without the PHP Async / Await RFC's conclusion, but it won't be in ideal form without it.

Containers provide process isolation. Say I have a WordPress plugin that I want to use composer libraries with. Say, Guzzle.  I can write my own container interface without touching Guzzle's code exposing the parts of Guzzle I need for my plugin. Since WordPress has no current core composer support I would need to carry a loader in my distro to be ran on the install hook of the plugin, bringing in composer and letting it in turn pull the libraries that I need to use.  If someone later writes a plugin that uses an incompatible version of Guzzle that doesn't matter - even if they lazily load it into the main symbol table by not using a container.  My plugin is isolated.

That isolation comes at a price. Each plugin has an independent vendor directory with no way to resolve complete redundancy. At least not in WordPress, which has no current composer implementation in core (I'm on the wp trac in a thread discussing it though).

There are open questions though that must be answered before this container idea is seriously explored. Most critical, can PHP be made to handle an object passing from one thread to another? That is, continuing from the example above, can a client instance from one GuzzleContainer coexist on the main thread with a client instance from a different container and referencing different code? Or will methods be unable to cross the barrier? What are the implications for the Reflection API and using it to examine an object that originated from another process thread referencing symbols that don't exist on the current thread?

The idea of containers is simple to state - but the implications are profound and might be unworkable. I simply don't know enough about the PHP engine to know.

This seems completely out of a "modules" scope to me: threading comes with a huge cost (like state interoperability, thread healthchecks, memory usage, and many other things), and this brings a lot of complexity that is not needed yet IMO. If someone wants to "sandbox" PHP somehow, it's already possible to execute another PHP script from inside a PHP script via CLI, and passing arguments to it, but there's no back-and-forth communication, only a "request-response-like" system.

Reply via email to