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.