# Two-step creation for PHP modules

- Thread: https://externals.io/message/131171
- Original archive: https://news-web.php.net/php.internals/131171
- Started by Alex Rock on 2026-06-08T15:36:17+00:00
- Messages: 27
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/131171.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #131171 — Alex Rock — 2026-06-08T15:36:17+00:00

URL: https://externals.io/message/131171#131171

Again, another "modules" proposal, but in more steps.

(sorry, this is a very long post).


===


TL;DR: the final goal (in a long time, possibly) is to be able to do 
something like this:

<?php declare(module=1);
// some_file.php

export class SomeClass {
     public function someMessage() {
         myInternalFunction();
     }
}

function myInternalFunction() {
     echo "Hello world!";
}

<?php
// index.php
import SomeClass from 'some_file.php';

$o = new SomeClass();
$o->someMessage(); // Works.
myInternalFunction(); // Fatal error: undefined function "myInternalFunction".


===


Trivia:


Recently, we had proposals for "Friend classes" and "Pure PHP files". 
These suggestions aren't new at all, but they demonstrate some wish in 
the PHP ecosystem to make certain changes for PHP to be closer to other 
programming languages.

On my side, I've reviewed some of the discussions regarding "modules", 
and though it's quite messy because there are lots of different views 
and opinions, I think, maybe too optimistically, that there might be a 
way to pave the way to "PHP modules" in another way than a huge change 
in the entire engine.


I think we can implement actual "modules" in two steps:

- Implement *definition files* first, so they can be handled in the 
compilation step and have no runtime effect (see later).
- Implement "*modules*" with an "import" keyword, and make "import"-ed 
modules in a way that a "module" is only a definition file that can 
"export" some of its defined structures and/or "import" structures from 
other modules, with a completely enclosed and standalone scope/context.


===


First step: *definition files*.

One of the proposals for "modules" implied files with a different PHP 
extension, to make them easily distinguishible from other files, and the 
recent "pure files" suggestion follows the same idea: removing the 
"<?php" tag so that PHP doesn't need it anymore.

However, these discussions had certain conclusions that I agree with:

- Making the extensions different will profoundly change how PHP 
includes files. Extensions like ".inc", ".php5" or alike were 
discouraged for specific reasons, and nowadays, most PHP handlers 
(apache, nginx, caddy, and possibly others) are defaulted to ".php" 
extension files, so adding a new extension means changing both the 
engine and the whole ecosystem. The conclusion is overall that changing 
the extension will not give any benefit, and only brings disadvantages.
- "pure" files, as in "no open/close tags" brings no real value, because 
having "<?php" is similar to having a shebang line in many other file 
types, and even PHP files themselves can contain a shebang line, so it's 
already a nice indicator, and ALL tooling around PHP code needs them to 
distinguish PHP code from "non-PHP" whatever-they-are characters.

On my side, when I first read the "pure" proposal, I was thinking mostly 
about "pure" as in "has no side-effect".

Which brings another idea: what if include-ing a PHP file actually had 
zero side-effect, apart its compilation process?

That's where I'm coming with this idea: *definition files:*

 > Notes:
 > - I will often refer to "built-in" in here, and "built-in" means 
"built in PHP or one of the enabled extensions", which implies 
"accessible at compile-time"
 > - When I say "global scope", I also imply "global namespace scope" 
for every namespace defined in the file.

  * A PHP "definition" file is a file that has *ZERO global state,
    calls, or mutable statements*.
  * It is declared with a `declare(def=1);` statement at the top of it.
  * It can only contain *declarations*: `(include|require)(_once)?`
    `const`, `function`, `namespace`, `class`, `return`, `interface`,
    `trait`, `use`, `enum`, etc.
  * It can *include/require* a file, as long as this statement is a
    *string literal* (or a built-in constant)
  * Since the file must not contain statements, the global scope of the
    file must not refer to any variable, and must not define variables
    either. Even superglobals.
  * `if`/`else`/`elseif`, `switch` or `match` statements can also be
    allowed, only if they respect the previous points. This way, you can
    still define functions/constants/classes depending on PHP versions
    (see next points about constants). I'm not sure about iterators
    (`for`, `while`, `do...while` or `foreach`), because I see no proper
    use-case, but they can still be allowed if they imply no global call
    statements, though it seems very unlikely anyway.
  * `try/catch/finally` are useless if global scope has no calls, so
    they can be safely forbidden.
  * Statements like `break`, `continue` are not allowed either, because
    they implicitly expect a "parent context".
  * `new` is allowed for built-in classes only, and can only receive
    literals (because it cannot refer to variables or user-based constants.)
  * `exit/die` are also forbidden in the global scope, and should be
    replaced with exceptions.
  * `throw` is allowed, but can only throw exceptions from built-in
    eceptions. User-created exceptions are not allowed.
  * Global scope can never contain the closing tag `?>`, as a safety
    against potential "echo" calls. It /might/ use it as only last
    characters of said file, but IMO it's much easier to handle the "no
    closing tag" case than "possible as last statement of the file". The
    problem is that having a closing tag at the end can mess with IDEs
    that ensure a line feed at the end of every file (file that will
    therefore have an `echo "\n";` statement in it...)
  * The file's global scope can refer to constants defined internally by
    PHP or its extensions. This means every constant that isn't in the
    `user` key when calling `get_defined_constants(true)` in PHP, as
    well as magic constants like `__DIR__`. Only constants that are
    always available at compile-time will be checked. This way, it can't
    accidentaly trigger an `Undefined constant` warning for userland,
    but it can trigger one if a native extension isn't enabled or
    doesn't have said constant, which can also be detected at compile-time.


This concept brings more advantages than previous proposals:

  * Your file is still normal PHP, compatible as usual.
  * Can still be interpreted by all IDEs that support PHP code
  * Doesn't need a different file extension
  * The fact that it's a definition file is explicitly visible at the
    beginning of the file when you open it
  * It can still be included/required by any other file without the file
    itself knowing that it actually includes a "definition file", and is
    therefore fully eligible to be compatible with all current autoload
    setups, including Composer
  * Still allows things that frameworks do for conditional
    function/class declaration (if it relies on the PHP version
    constant, for example). Will not be able to use `version_compare()`
    though, but there are workarounds.
  * Potential compile-time built-in constant optimization (if not
    already done by the compiler, I didn't search for this yet)
  * Everything that is not global/namespace-scope (functions, classes,
    etc.) can still contain whatever code they need, and theoretically
    it can even contain the PHP closing tag, since it's compiled as an
    "ECHO" statement.
  * All potential errors when including/requiring such file will be
    compile-time errors, therefore if the file is "correct", compiling
    it definitely means that it has its place in the opcache for a very
    long time as no runtime can alter its global context.
  * Having no actual call statements in the global/namespaced scope
    ensures no "echo", but overall has absolutely zero runtime impact
    other than compile-time errors, since there cannot be notice/warning
    errors that might also pollute the current buffer. (I might have
    forgotten what else can throw a notice/warning, but feel free to
    correct me if I do).

There are only a tiny amount of drawbacks to this (from what I've 
thought about so far):

  * All definition files will have to begin with `<?php declare(def=1);`
    (fair enough IMO, since some static analysers are already capable of
    adding "declare(strict_types=1)` automatically...)
  * Potential tiny compile-time performance drop and/or memory
    consumption, because all global statements would have to be checked
    and analysed. And maybe a bit more if constants are also validated.


For end-users, a "definition" file has only one single advantage: it has 
no runtime impact when being loaded, and only when its defined 
structures are used. This is a guarantee of trust that can benefit all 
frameworks and libraries.

But this advantage paves the way to "modules" in a very interesting 
manner: all modules *must* be definition files in the first place.


===


Second step: *PHP modules*.

 > TL;DR: loading a "module" is similar to an "include/require"_once, 
but at the compiler-level instead of the engine/runtime-level.

The concept of "module" in my mind in PHP is the following:

A *PHP Module* is a normal PHP file that is, at first, a *definition 
file*, but instead starts with `declare(module=1);`. This declaration 
automatically implies `declare(def=1);`, and the wrong combo 
`declare(module=1,def=0);` can throw a compile-time error.

On the Module side:

  * It has access to new keywords: `import` and `export`, as well as
    `import ... as ...` and `export ... as ...`.
  * Module names are useless, since the module is the file itself.
  * A module can `export` whatever is declared in said file: constants,
    functions, classes, enums, interfaces..., as long as it's only a
    declaration and not an actual call.
  * The `export` keyword must implicitly be in the global namespace,
    even if it is written inside another namespace.
    This implies that these variants have to be considered strictly
    equivalent:
    ```
    <?php declare(module=1);
    namespace My\Namespace;
    export class MyClass {};

    <?php declare(module=1);
    namespace My\Namespace {
         export class MyClass {};
    }

    <?php declare(module=1);
    namespace My\Namespace {
         class MyClass {};
    }
    export MyClass;
    ```
    is the exact same as the following:
  * Composer can add a new package type named "php-module" that accepts
    only one single PHP file as input, that file must be a module.
  * A module can import other modules.
  * Conditional, or encapsulated `import` can also be resolved at
    compile-time, and since they can only contain compiler-accessible
    statements (string literals or built-in constants), the behavior
    will be similar to an "include" statement at runtime on a definition
    file anyway.
  * Unused imports can be detected at compile-time and throw a notice
    message
  * (my opinion, so definitely optional) Two exports must never have the
    same name. By this, I mean that you could use `import someFunction,
    SomeClass, SOME_CONSTANT from 'file.php';` freely without having to
    specify *what* you are trying to import. This is of personal taste,
    to enforce users not to use the same names to avoid confusion in
    general. I see no proper use-case to allow constants and classes to
    have the same name, for example, but some people might dislike this.
    On the engine-side it will still properly define the expected
    structures from the module, and on the userland, errors will be
    thrown if said structure is used improperly anyway. To me,
    considering how big this feature is, this is just a way to
    "opinionate for better naming" :) (and it avoids ugly things like
    `import const SOME_CONST as SOME_CONST_ALIAS, class SomeClass as
    SomeClassAlias, function someFunction as someFunctionAlias from
    'file.php';`, right?)

And on the userland-side:

  * The `import` and `import ... as ...` keywords also becomes
    accessible to ANY other PHP file, whether it is a definition, a
    module, or a regular PHP file.
  * Just like in definition files, the `import` keyword can only refer
    to string literals and/or built-in constants. This restriction is
    only applied for the new `import` keyword, and not to the rest of
    the file.
  * The `import` keyword will explicitly make all imported structures
    accessible in the current file, just like if the module was loaded
    with `include`, but only `export`-ed structures are accessible.
  * Imports can be placed anywhere in the file, and will be resolved at
    compile-time, since their only drawback is "adding more structures
    in memory".
  * An import can define an alias that will only be accessible to the
    importer-file, like `import SomeClass as MyAlias from 'file.php';`.


Internally, there are other interesting things that happen:

  * All definitions from inside a module will have be prefixed in the
    symbols table with a hash corresponding to the current module file's
    hash. It can be similar to how anonymous classes are registered
    internally. The goal is to make them inaccessible (as much as
    possible) from the global scope.
  * All calls to module file's internal definitions will use this hash
    prefix to refer to said structures.
  * When using "import", it will do 3 things:
      o Analyse `import`-ed statements, to retrieve only the structures
        that are asked by the end-user
      o Load the file (from file, or from opcache, if not already in memory)
      o Create the modules definitions (if not already in memory) with
        internal hashes as previously described, and tree-shake unused
        structures as of the list of `import`-ed ones. Can be done at
        importer-compile-time too.
    This way, it would behave similarly to `(require|include)_once` but
    at a more granular level: with only the structures that were
    imported by the current file. Any subsequent call to `import` for
    the same file will do the same thing, and since these files have no
    runtime impact and only contain definitions, it should have close to
    no loading impact. Subsequent imports with the same structures will
    load only the ones that are already in memory (because they can be
    referenced with the hash-prefix), and if a "new structure" is found
    that has not been loaded already in the global space, it will create
    it in the global scope at runtime. This makes sure that module files
    with 100 exports will not load /all/ structures in memory when the
    file is `import`ed.
  * Modules have no impact on autoload, since they don't function the
    same way.
  * Since internal functions/constants/etc. are hash-prefixed, they will
    never conflict with other internal structures. This means that a
    module could define the `str_contains` function, if it wanted. And
    it could even reuse the native function by using `use function
    str_contains as base_str_contains`. Would also work for
    object-oriented structures (class, interface, etc.) as well as
    constants too.
  * We can create a *`ReflectionModule`* class, which constructor
    accepts a file name, and throws an exception if the file is not a
    module. This class would expose the list of exported structures from
    said module. Maybe it can also contain the processed hash/prefix and
    the internal structures too, but having these available kinda
    defeats the purpose of having internal structures in the first
    place... But the exported structures would be ReflectionClass,
    ReflectionConstant, etc, with the "module" flag explained in next point:
  * Other Reflection classes will contain an internal "defined in
    module" flag, as well as a nullable string corresponding to the path
    to the module file if the structure is effectively defined in a module.
  * FQCNs will always resolve to the "global public name", and never to
    the internal hash-prefixed name.
  * A new global function can be used: `spl_register_module($prefix,
    $filePath);`. This way, we could definitely imagine a flexible
    prefix to resolve to a module path for `import` statements. It
    allows Composer-package-compatible syntax like `import Request from
    '@symfony/http-foundation/Request.php';` being registered with
    something like `spl_register_module('@symfony/http-foundation',
    __DIR__.'/vendor/symfony/http-foundation/');`
  * The `spl_register_module` function can refer to structures directly,
    if needed: `spl_register_module('@symfony/http-foundation/Request',
    __DIR__.'/vendor/symfony/http-foundation/Request.php');`.
    This allows for no-extension imports like `import Request from
    '@symfony/http-foundation/Request';` (but this is just for the fancy
    looks)
    The function itself will register the input as a prefix or as a
    module path based on whether the specified path is a directory or a
    file, checked at runtime.
  * Multiple paths can be used for the same prefix, as long as they are
    all directories.
  * If a module is registered as a file instead of a directory, there
    can be only one.
  * (yeah, I know, this concept looks like a reinvention of
    include_paths, but hey, it's modules now!)
  * Autoload-like features can be used for projects using Composer:
      o The `composer.json` file can contain new field: "modules" and
        "modules_dev".
      o This field would contain a key=>value list of prefix=>file_path
        items, that Composer will register through the aforementioned
        new spl_register_module() function.
      o Composer will (sorry folks) need a way to make sure two PHP
        packages don't contain the same module prefixes resolving to two
        paths, regardless of them being directories or files. Maybe
        Packagist (sorry again) will need this too. This is important to
        avoid vendor name squatting in modules.
  * These module-autoload rules would not change anything at existing
    autoload, but they would mostly be here to map a PHP package with
    its exposed API.
  * This also makes sure that any PHP package can say "All API exposed
    as a module is covered by BC policy, all the rest is not". Easier
    for maintainers to keep their internal stuff, and a bit easier to
    make the Open/Closed principle available at a package-level instead
    of a class-level.


===


I already worked on the first step to "PHP definition files" and made a 
PR of it: 
https://github.com/php/php-src/compare/master...Pierstoval:php-src:defs

With my tiny knowledge of PHP internals, I required the help of Cursor 
for that, and I added a lot of `.phpt` test files to ensure the basics 
are covered, built the project & ran all the tests on my LMDE 7 (Debian) 
machine multiple times with different configs (embed, fpm, debug, etc.), 
everything works so far.
Apart the tests, the PR seems quite light, but it obviously needs 
thorough review (or rewrite...) before even being converted into an RFC. 
It just had the advantage of being fully ready and thoroughly tested 
(hopefully I didn't forget anything) in less than a day...

/> Note: I did NOT use any llm to write this message, it was only used 
for some bits of code in the above PR, nothing more./

If you have read everything down to this line, thank you very much! It's 
the fruit of quite some work!


Now to you folks, it's yours to take and talk :)


-- 
Alex "Pierstoval" Rock
Polydisciplinary professional web development and training

---

## #131175 — Rowan Tommins [IMSoP] — 2026-06-08T21:00:31+00:00

URL: https://externals.io/message/131171#131175 · In reply to: #131171

> Recently, we had proposals for "Friend classes" and "Pure PHP files". 
> These suggestions aren't new at all, but they demonstrate some wish in 
> the PHP ecosystem to make certain changes for PHP to be closer to 
> other programming languages.


I don't think "being closer to other programming languages" is, or 
should be, an aim of the project. Our aim should be to make PHP as good 
a language as it can be.

That includes borrowing ideas which have proved successful in other 
languages, large and small, as well as avoiding pitfalls which have been 
revealed by those other languages. It also includes retaining PHP's 
identity as a distinct language with its own history, user base, and 
ecosystem.

As I've said in previous discussions, JavaScript's module system was 
designed around a very specific set of facilities and constraints. It 
has some similarity to Python, which uses some of the same concepts 
(e.g. all definitions are essentially anonymous objects, named by 
assigning to variables).

PHP has an entirely different set of facilities and constraints, much 
more similar to Java and C# (e.g. all definitions have a globally unique 
name, namespace imports are primarily short-hand for those unique names).

Your proposal looks very much like trying to wedge JavaScript's solution 
into PHP, rather than a realistic path forward for PHP's existing ecosystem.


-- 
Rowan Tommins
[IMSoP]

---

## #131176 — Michael Morris — 2026-06-09T00:37:22+00:00

URL: https://externals.io/message/131171#131176 · In reply to: #131175

On Mon, Jun 8, 2026 at 5:00 PM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>
wrote:

> 
> > […]
>
>
> I don't think "being closer to other programming languages" is, or
> should be, an aim of the project. Our aim should be to make PHP as good
> a language as it can be.
>
> That includes borrowing ideas which have proved successful in other
> languages, large and small, as well as avoiding pitfalls which have been
> revealed by those other languages. It also includes retaining PHP's
> identity as a distinct language with its own history, user base, and
> ecosystem.
>
> As I've said in previous discussions, JavaScript's module system was
> designed around a very specific set of facilities and constraints. It
> has some similarity to Python, which uses some of the same concepts
> (e.g. all definitions are essentially anonymous objects, named by
> assigning to variables).
>
> PHP has an entirely different set of facilities and constraints, much
> more similar to Java and C# (e.g. all definitions have a globally unique
> name, namespace imports are primarily short-hand for those unique names).
>
> Your proposal looks very much like trying to wedge JavaScript's solution
> into PHP, rather than a realistic path forward for PHP's existing
> ecosystem.
>
>
> --
> Rowan Tommins
> [IMSoP]
>

I see the bear is out for it's yearly walk eh? At least I wasn't the one
who woke it this time.

Kidding aside.

Changing the language for change sake is a non-starter Alex.  First, what
is the problem you want to solve?

The largest problem a module system could solve is resolving namespace
conflicts.  If you delve into the discussion Rowain and I had on this last
year it goes into detail on this to some degree, but the real world problem
is this. Suppose I write AkiPublish, a plugin for Wordpress which uses
composer to resolve dependencies.  Suppose someone else out there writes
BobMod, a plugin to help moderate.  For whatever reason the two plugins
want to use different incompatible versions of a library called LogIt.

The end user will have a crash if they ever install these plugins together.
And if they're like 90% of WordPress users they won't have a clue why.  PHP
has no mechanism for code isolation - plugins, extensions, modules or
whatever the heck you call them, they all must share a global state.
Careful coding mitigates most of this problem and it is the reason Laravel,
Symfony, Drupal et al take the form they do - avoiding establishing global
variables at all cost and putting this into classes. But that isn't the
architecture of WordPress and indeed most of PHP before 5.3.

And while many are dismissive of Wordpress, it does have one of the
largest, perhaps the largest, footprints in the PHP CMS space.

True code isolation would be useful to have, but it needs to take a form
that is minimally disruptive.  Rowain and I had worked out some concepts,
but I dropped the ball on this and got distracted with other projects. To
be honest, these days I spend as little time working with computer code
outside of work as I can because it just doesn't interest me anymore. I
wrestle with Dorico and try to actually "compose".

That said, at this time completion of the PHP-ASYNC project might be
prerequisite to doing this. The reason is simple - the best way to isolate
code packages is to put them on different threads. The moment that happens
calls to functions living on those threads will have to be handled
asynchronously. That's one of the reasons I've remained silent on this for
much of the last year.

---

## #131182 — Alex Rock — 2026-06-09T09:39:40+00:00

URL: https://externals.io/message/131171#131182 · In reply to: #131176

Le 09/06/2026 à 02:37, Michael Morris a écrit :
>
>
> On Mon, Jun 8, 2026 at 5:00 PM Rowan Tommins [IMSoP] 
> <imsop.php@rwec.co.uk> wrote:
>
>     
> > […]
>     files".
> > […]
>     wish in
> > […]
>
>
>     I don't think "being closer to other programming languages" is, or
>     should be, an aim of the project. Our aim should be to make PHP as
>     good
>     a language as it can be.
>
>     That includes borrowing ideas which have proved successful in other
>     languages, large and small, as well as avoiding pitfalls which
>     have been
>     revealed by those other languages. It also includes retaining PHP's
>     identity as a distinct language with its own history, user base, and
>     ecosystem.
>
>     As I've said in previous discussions, JavaScript's module system was
>     designed around a very specific set of facilities and constraints. It
>     has some similarity to Python, which uses some of the same concepts
>     (e.g. all definitions are essentially anonymous objects, named by
>     assigning to variables).
>
>     PHP has an entirely different set of facilities and constraints, much
>     more similar to Java and C# (e.g. all definitions have a globally
>     unique
>     name, namespace imports are primarily short-hand for those unique
>     names).
>
>     Your proposal looks very much like trying to wedge JavaScript's
>     solution
>     into PHP, rather than a realistic path forward for PHP's existing
>     ecosystem.
>
>
>     -- 
>     Rowan Tommins
>     [IMSoP]
>
>
> I see the bear is out for it's yearly walk eh? At least I wasn't the 
> one who woke it this time.
>
> Kidding aside.
>
> Changing the language for change sake is a non-starter Alex.  First, 
> what is the problem you want to solve?
>
> The largest problem a module system could solve is resolving namespace 
> conflicts.  If you delve into the discussion Rowain and I had on this 
> last year it goes into detail on this to some degree, but the real 
> world problem is this. Suppose I write AkiPublish, a plugin for 
> Wordpress which uses composer to resolve dependencies.  Suppose 
> someone else out there writes BobMod, a plugin to help moderate.  For 
> whatever reason the two plugins want to use different incompatible 
> versions of a library called LogIt.
>
> The end user will have a crash if they ever install these plugins 
> together. And if they're like 90% of WordPress users they won't have a 
> clue why.  PHP has no mechanism for code isolation - plugins, 
> extensions, modules or whatever the heck you call them, they all must 
> share a global state. Careful coding mitigates most of this problem 
> and it is the reason Laravel, Symfony, Drupal et al take the form they 
> do - avoiding establishing global variables at all cost and putting 
> this into classes. But that isn't the architecture of WordPress and 
> indeed most of PHP before 5.3.
>
> And while many are dismissive of Wordpress, it does have one of the 
> largest, perhaps the largest, footprints in the PHP CMS space.
>
> True code isolation would be useful to have, but it needs to take a 
> form that is minimally disruptive.  Rowain and I had worked out some 
> concepts, but I dropped the ball on this and got distracted with other 
> projects. To be honest, these days I spend as little time working with 
> computer code outside of work as I can because it just doesn't 
> interest me anymore. I wrestle with Dorico and try to actually "compose".
>
> That said, at this time completion of the PHP-ASYNC project might be 
> prerequisite to doing this. The reason is simple - the best way to 
> isolate code packages is to put them on different threads. The moment 
> that happens calls to functions living on those threads will have to 
> be handled asynchronously. That's one of the reasons I've remained 
> silent on this for much of the last year.
>
>

Thanks Michael and Rowan for your quick insights, especially since I 
have read some of your past conversations about Modules :)


So, let me try to answer your questions.


 > First, what is the problem you want to solve?

The main problems that PHP modules solve are the following:

- Libraries can finally isolate code completely, and not only in private 
class methods, but they can isolate entire structures, and can even 
isolate a sub-library
- Since all modules internally contain a hashed-prefix version of all 
their definitions, two versions of the same library can coexist, since a 
module hash is unique based on its file path and contents (I should have 
made it explicit that the module prefix is hashed based on file contents 
& path, to ensure uniqueness)

Many existing libraries could migrate parts of their internal structures 
(the ones not supposed to be supported by their BC policy) to modules 
with no impact on userland code.

And the future of this might help improving the ecosystem by providing a 
different way to package applications themselves: if the first loaded 
file is a module, and all files in the module tree are also modules and 
all "include/require" are done on compiler-resolvable paths, this means 
that an entire PHP app could be bundled as the opcodes. It can already 
be bundled like that, but it needs a few hacks, whereas modules make it 
directly built-in. We could imagine the FrankenPHP worker mode have one 
base memory impact based on the modules tree, and all the rest would be 
I/O-related memory consumption.

Having everything made into modules also allows giving more power to the 
compiler and engine in order to control stack/heap-related structures: 
garbage collection points can be more deterministic, objects references 
can be tracked a bit more easily, and so on.


 > I don't think "being closer to other programming languages" is, or 
should be, an aim of the project. Our aim should be to make PHP as 
good a language as it can be.

You're right, and that's the reason why, since modules have been 
requested for some time now, I think the best way to have this feature 
is to ensure its integration smoothly in the compiler and engine.


 > True code isolation would be useful to have, but it needs to take a 
form that is minimally disruptive.  Rowain and I had worked out some 
concepts, but I dropped the ball on this and got distracted with other 
projects. To be honest, these days I spend as little time working with 
computer code outside of work as I can because it just doesn't interest 
me anymore. I wrestle with Dorico and try to actually "compose".

That's why my thoughts are about doing it in two steps: first make sure 
that a module is "harmless when loaded", ensuring proper impact-free 
compilation and structure-definition, so that the compiler can guarantee 
that such compiled file is "safe", or "pure" (in the means of "no impact 
on external code", apart compiler-detectable issues, like existing 
classes, constants, etc.), and that userland can still use this file 
without BC breaks. And as said, this "simple feature" makes all existing 
codebases still work the same.


As I already said: the main goal isn't to resemble JS or other 
languages. It's to solve the aforementioned problems in a way that can 
be both close to existing languages (for familiarity), and smooth on the 
ecosystem (which the proposed two steps are for the ecosystem, but the 
second step is heavy on the engine).

---

## #131189 — Rowan Tommins [IMSoP] — 2026-06-09T20:34:45+00:00

URL: https://externals.io/message/131171#131189 · In reply to: #131182

>
> Many existing libraries could migrate parts of their internal 
> structures (the ones not supposed to be supported by their BC policy) 
> to modules with no impact on userland code.
>

I think this is the crux for me, and why I reacted as I did to your 
initial e-mail. It doesn't look anything like how PHP libraries lay out 
their code today, so migrating to it would involve a complete change of 
coding style.

In JavaScript, it has always been common practice to ship libraries as a 
single file, so the user only needed to add a single <script> tag. 
Inside that file, a single function creates a private scope, 
and everything declared is hidden unless passed out in some way. The 
modern module format still basically works that way: "import" references 
a file, and "export" defines objects to pass back.

In PHP, we've always had include/require, and for the last 20+ years, 
we've had autoloading, so packages commonly have many small files. 
Libraries have always prefixed names to avoid colliding with other code, 
and namespaces made that easier. Loading a package usually means 
configuring an autoloader to look in directory X for namespace prefix Y, 
and reference the classes you need by name.

Changing every file in a library to use import and export statements 
would be a huge chore, and break all sorts of assumptions, for very 
little benefit. The natural boundary in PHP is not an included file, 
it's a registered namespace prefix.



> And the future of this might help improving the ecosystem by providing 
> a different way to package applications themselves: if the first 
> loaded file is a module, and all files in the module tree are also 
> modules and all "include/require" are done on compiler-resolvable 
> paths, this means that an entire PHP app could be bundled as the opcodes.
>

OpCache already handles the difference between per-file compilation and 
"linking" between files; the reason not to ship opcodes is more about 
the stability of the OpCode format itself, which can change in even 
minor versions.

Module-level optimisation would potentially be a significant benefit, 
but again the key there is how to define a boundary around *multiple* 
files, to cache them as a single unit. If the cache key has to include 
*extra* granularity, for "file X imported from file Y" vs "file X 
imported from file Z", that just leads to more cache misses.


-- 
Rowan Tommins
[IMSoP]

---

## #131190 — Larry Garfield — 2026-06-09T22:24:25+00:00

URL: https://externals.io/message/131171#131190 · In reply to: #131182

> > […]
>
> The main problems that PHP modules solve are the following:
>
> - Libraries can finally isolate code completely, and not only in 
> private class methods, but they can isolate entire structures, and can 
> even isolate a sub-library
> - Since all modules internally contain a hashed-prefix version of all 
> their definitions, two versions of the same library can coexist, since 
> a module hash is unique based on its file path and contents (I should 
> have made it explicit that the module prefix is hashed based on file 
> contents & path, to ensure uniqueness)
>
> Many existing libraries could migrate parts of their internal 
> structures (the ones not supposed to be supported by their BC policy) 
> to modules with no impact on userland code.

See, I don't think this is remotely true.

Consider Serde.  (My go-to example.)  It contains dozens of class-likes.  A typical serialization request is going to use 90% of them.  Being able to front-load and link all of them without going through the autoloader every time sounds good!  But...

There is no way in hell that I'm moving dozens of classes into a single file.  Multi-thousand-line files are frowned upon for a reason.  In practice I don't really need most of them to be private.  Maybe one, I dunno.  So I simply wouldn't bother., meaning I wouldn't benefit from whatever performance optimizations we are able to add later.

So this approach makes modules something usable ONLY by code bases that have lots of defined class-likes or functions that are "private", AND they're all very very small so that the resulting mega-file isn't too large for my IDE to open.  That's a very, very small number of cases.

I will reiterate what Rowan said above, and what I said the last time modules were discussed: module == file is absolutely a dead-end for PHP.  It simply will not work in practice.  Not because of PSR-4 like some people keep claiming, but because the resulting files would be just too damned big and unwieldy.

Add to that, how do I write tests for the "private" classes?  I should still be able to test those on their own, without having to go through the few public facing classes.  If the tests are in a separate file... how do I do that?

Let's stop trying to make module == file happen.  It's not going to happen.

--Larry Garfield

---

## #131192 — Alex Rock — 2026-06-10T07:36:44+00:00

URL: https://externals.io/message/131171#131192 · In reply to: #131189

Le 09/06/2026 à 22:34, Rowan Tommins [IMSoP] a écrit :
> 
> > […]
>
> I think this is the crux for me, and why I reacted as I did to your 
> initial e-mail. It doesn't look anything like how PHP libraries lay 
> out their code today, so migrating to it would involve a complete 
> change of coding style.

That's the goal here: keep BC at all costs, and make modules transparent 
in userland. Change of coding style is opt-in for libraries/frameworks 
maintainers that want internal structures whenever they need it.


>
> In JavaScript, it has always been common practice to ship libraries as 
> a single file, so the user only needed to add a single <script> tag. 
> Inside that file, a single function creates a private scope, 
> and everything declared is hidden unless passed out in some way. The 
> modern module format still basically works that way: "import" 
> references a file, and "export" defines objects to pass back.
>
> In PHP, we've always had include/require, and for the last 20+ years, 
> we've had autoloading, so packages commonly have many small files. 
> Libraries have always prefixed names to avoid colliding with other 
> code, and namespaces made that easier. Loading a package usually means 
> configuring an autoloader to look in directory X for namespace prefix 
> Y, and reference the classes you need by name.

My proposals for modules do not imply that everything is shipped in one 
single file. It implies that a module allows "hiding" some of its 
internal structures instead of exposing everything to the global space, 
so that instead of adding "@internal" or "final" to everything to avoid 
users extending such code, you can just ship that internal code along 
the rest.


>
> Changing every file in a library to use import and export statements 
> would be a huge chore, and break all sorts of assumptions, for very 
> little benefit. The natural boundary in PHP is not an included file, 
> it's a registered namespace prefix.

As said: it's not mandatory. And namespaces can be overriden at will in 
userland, while internal module code cannot (unless you succeed in 
hacking the hashed prefix and sort of extend that, but modules should 
make this impossible, and if not, as hard as possible).


>
>
>
> > […]
>
> OpCache already handles the difference between per-file compilation 
> and "linking" between files; the reason not to ship opcodes is more 
> about the stability of the OpCode format itself, which can change in 
> even minor versions.
>
> Module-level optimisation would potentially be a significant benefit, 
> but again the key there is how to define a boundary around *multiple* 
> files, to cache them as a single unit. If the cache key has to include 
> *extra* granularity, for "file X imported from file Y" vs "file X 
> imported from file Z", that just leads to more cache misses. 

In my mind, the goal is to deport runtime "a list of compiled files are 
executed" concept into a compile-time "when importing a module, its ast 
is directly copy/pasted here, with safeguards to avoid redefining the 
same structures in the symbols table". Since a module file will contain 
a hashed prefix (based on file path & content), it's (almost) impossible 
to have two similar hashes that would conflict with each other, and the 
safeguard is only here to make sure two files can import the same module 
and reuse the defined structures directly from memory if they were set 
already.

A module doesn't necessary need to be a single unit: as said, you can 
still have a "PSR-4-way of implementing modules", where modules are all 
exporting one single PHP class, and it shouldn't make any difference in 
the first place. The differences will be in the internal structures that 
can be necessary for said PHP class. Sure it might break PSR-4 itself, 
hence why I do *not* suggest implementing true PSR-4 on modules, because 
that's not really the same thing, but their similarity allow for 
internal data structures, constants, etc., that allow maintainers a bit 
more freedom on their BC policy.

---

## #131193 — Alex Rock — 2026-06-10T08:40:37+00:00

URL: https://externals.io/message/131171#131193 · In reply to: #131190

Le 10/06/2026 à 00:24, Larry Garfield a écrit :
> 
>
> > […]
> See, I don't think this is remotely true.
>
> Consider Serde.  (My go-to example.)  It contains dozens of class-likes.  A typical serialization request is going to use 90% of them.  Being able to front-load and link all of them without going through the autoloader every time sounds good!  But...
>
> There is no way in hell that I'm moving dozens of classes into a single file.  Multi-thousand-line files are frowned upon for a reason.  In practice I don't really need most of them to be private.  Maybe one, I dunno.  So I simply wouldn't bother., meaning I wouldn't benefit from whatever performance optimizations we are able to add later.

It always depends on the libraries: if Serde has structures that *must 
not* be accessible from userland, they would be a great fit for being 
added to your module file as internal structures. If they can be used by 
users for something else than full serialization/deserialization, then 
of course they have to be part of the public API. It all depends on how 
you view your library, how you want to expose your code, and how 
comfortable you would be with maintaining a bigger public API whereas 
you could actually maintain internal code with no BC breaks if you keep 
the public API but change the internals. As of today, there's zero way 
to prevent this natively in PHP, and the only workarounds are adding 
"@internal" phpdoc everywhere, which is only interpreted by static 
analysis, which we already know isn't a globally implemented dev workflow.

Again: many existing PHP projects that didn't embrace the PSR-0/4 
autoload norms, don't use Composer, or other tooling that have huge 
legacy non-standard codebases like Wordpress or Dolibarr, would be able 
to benefit from modules for something different than just "internal 
structures", being the possibility to have two versions of the same 
library used by different parts of their codebases (for 
plugins/extensions, mostly), which is a huge change in how these 
projects could evolve in the future, because they currently have no way 
to do this, and not even proper workarounds (apart potential 
class-prefixing, similar to what Box-project can do for PHAR files, but 
this is quite hard to implement for these projects).


> Add to that, how do I write tests for the "private" classes?  I should still be able to test those on their own, without having to go through the few public facing classes.  If the tests are in a separate file... how do I do that?

Just like you do when you have to test private class methods: you don't. 
It's not a good practice. Testing must be done on the public API anyway, 
and internal/private code must only be considered as an unreachable 
black-box.
Though, my proposal doesn't necessarily imply that testing internal 
structures is impossible: I still say that the proposed ReflectionModule 
class /can/ include the hashed prefix, and an access to the internal 
structures. All of internal structures are just "normal structures" but 
they are just not accessible from the global scope.
This ReflectionModule class could give you access to the hash-prefixed 
fully-qualified-names of all internal structures, and if they are 
functions, you could still use ReflectionFunction on them to call them 
from tests, and if they are classes, you could use ReflectionClass to 
create a new instance.
It's not impossible, it's just that it's the same hacks that you have to 
do when testing private methods, and not a good practice overall.


> So this approach makes modules something usable ONLY by code bases that have lots of defined class-likes or functions that are "private", AND they're all very very small so that the resulting mega-file isn't too large for my IDE to open.  That's a very, very small number of cases.
>
> I will reiterate what Rowan said above, and what I said the last time modules were discussed: module == file is absolutely a dead-end for PHP.  It simply will not work in practice.  Not because of PSR-4 like some people keep claiming, but because the resulting files would be just too damned big and unwieldy.


Ok, I thought that two-step PHP modules was already big as a suggestion 
and didn't want to dig into too much "new ideas". Two are already a lot.
But... the problem you think modules create (aka "huge files with one 
single exported public-API-related class") can be solved with another 
concept that PHP doesn't have, but that has been requested for quite a 
long time. I have already thought about it when sending my first message 
to internals, but I was afraid it would backlash a bit, and/or overwhelm 
the readers.


So, as an avant-première, here's my *third step proposal* after modules: 
*packages*.


I already thought about how PHP packages can work, and instead of 
relying mostly on namespaces (like the existing proposals), packages 
would rely on two things: package "main file", and "package-included" files.

The *packages* system, in my mind, would work similarly to how Rust 
crates are defined.

Conceptually, a PHP package can only contain *modules*.

A PHP package *main file* would look like this:

```
<?php declare(module=1);
// serde/main.php

main Crell\Serde; // Unique namespace-like name.

// All these imports are resolved into files
import Serializer from 'serialize.php' as module;
import Deserializer from 'deserialize.php' as module;
import SERDE_VERSION 'some_internal_code.php' as module;

export Serializer; // Module syntax. Exports the public API from this module. No namespace needed.
export Deserializer;

// Example of public export using internal non-exposed constant:
export class Serde {
     public static version(): string { return SERDE_VERSION; }
};
```

How it is used:


```
<?php declare(module=1);
// serde/serialize.php

package Crell\Serde;

export Serializer from './src/Serializer.php';
```

How does it work?

First file content must be `main`: it declares the main file for a package.

All "import ... as module" statements in a "main" file are considered 
similarly to any imported module, but it adds a new feature: packaged 
modules.

A file imported as a module must contain the "package" declaration, it 
means that once the compiler encounters it, it checks the module tree to 
ensure that such module is loaded ONLY by a file that is *inside* the 
main package's module tree. (in the above case: Crell/Serde, for 
instance, which must be repeated in all modules of this package).

This means that, from this stage, the compiler will *prevent* ANY other 
PHP file from including, requiring or importing this packaged module.

With such safeguard, it would be extremely annoying for end-users to do 
something like this: `import SERDE_VERSION from 
'vendor/crell/serde/some_internal_code.php'`, because they would need to 
create a custom file that replaces `vendor/crell/serde/main.php`, 
copy/paste its content and change whatever they want, then they might 
need to override the spl module path registration if they wanted to 
override any other file from this vendor dir, and so on. Though it would 
be possible, the task would be extremely tedious, similarly to how 
nowadays we use to override library classes with dirty hacks, 
reflection, aliases, or autoload-based overrides that change the PHP 
code before loading it.

And something more is implied to all declared structures (as said, 
inspired by Rust): package-level structure visibility.

In Rust, when you create a file, all structures you declare are, by 
default, internal. Instead of using "export" like in JS, you can allow 
other files to use it if you use the "pub" keyword. But "pub" makes it 
part of the entire public API. That's when the "pub(crate)" keyword 
comes: it allows public visibility only for the package you're creating 
(from its "main" file), making internal code accessible from anywhere in 
the library, but inaccessible from the global scope.

With Packages, thanks to the `package` keyword used in the package's 
modules, any "export"-ed code from modules is only accessible from the 
modules declared in the *main file*. The main file's goal is to expose 
the package's public API.


This would allow existing libraries to have everything as internal 
classes, and instead of having one file with all structures, packages 
would allow to scatter all files in a PSR-4 way, whether they are 
internal or part of the public API.


Sure, this has drawbacks:

- Creating a "package definition" file that defines the list of modules 
for said package
- Exposing the public API from the main file, instead of "every module 
can determine whether they are public API or internal to the package.
- Adding "declare(module=1);" and "package ..." statements to all 
package files.

IMO, if you want "true" packages that behave as black-boxes with a 
public API and inaccessible internal code while still being able to 
develop a PHP library with classes scattered in a PSR-4 structure, there 
isn't a better way to do so. The package-internal-only structures can 
only become package-internal if we have either a standardized directory 
structure (which is IMO a bad idea for PHP, because we don't enforce 
file names neither extension, so I don't see a benefit of enforcing 
directory structure), or if we have a package-definition file.


That's where my research stopped, because this idea has some limitations 
that I have not fixed yet, like "feature flags for conditional modules" 
(again, inspired by Rust, but quite complex), or testing (aka "make 
these modules accessible in testing, but not in prod", which would imply 
a PHP-level flag, like a "testing = true" INI directive or something 
similar).
My inspiration from Rust doesn't help, because Rust has a built-in test 
framework, whereas PHP relies on 3rd-party frameworks (like PHPUnit), 
and these frameworks might need full access to internal structures from 
a library. But the ReflectionModule can be extended into a 
ReflectionPackage, so that internal structures can also be accessible, 
and PHPUnit could include a public API to instantiate objects from 
internal classes. But I preferred to stop my research here, since the 
creation of "definition files" and "php modules" already triggers too 
much :)



For the rest, I am still convinced that such vision of PHP Modules is 
completely harmless to existing frameworks, can bring tons of benefits 
to non-standard PHP projects, and would allow legacy projects to use 
different versions of the same PHP library (thanks to the prefix system).

PHP packages is just an extension of this in order to solve one more 
problem: internal code scattered in multiple files, instead of being in 
one big single file.


IMO, there's no way to introduce PHP packages if PHP itself doesn't 
narrow down how PHP files behave in the first place, hence why "multiple 
harmless steps" seems like the best compromise to me.

---

## #131194 — Rowan Tommins [IMSoP] — 2026-06-10T10:23:53+00:00

URL: https://externals.io/message/131171#131194 · In reply to: #131192

>Le 09/06/2026 à 22:34, Rowan Tommins [IMSoP] a écrit :
> > […]
>
>That's the goal here: keep BC at all costs, and make modules transparent in userland. Change of coding style is opt-in for libraries/frameworks maintainers that want internal structures whenever they need it.


I'm not talking about BC, or forced migration; I'm talking about the effort required when you *do* want the new facilities.

It's not at all clear to me where an existing library would add "export" and "import" keywords in order to mark some classes as internal. It would seem to require completely rearranging the project structure, and rewriting references to all the moved parts.

Compare to the work required to use a "namespace visibility" feature: 

1. Add a keyword, or an attribute, at the top of each internal class, limiting its use to a certain namespace prefix.
2. There is no step 2. Nothing needs to be moved, or renamed, or accessed differently.

That's what I mean by starting with how PHP is used today, and finding ways to enhance it.



Rowan Tommins
[IMSoP]

---

## #131196 — Alex \"Pierstoval\" Rock — 2026-06-10T13:03:49+00:00

URL: https://externals.io/message/131171#131196 · In reply to: #131194

Le 10/06/2026 à 12:23, Rowan Tommins [IMSoP] a écrit :
> 
> > […]
>
> I'm not talking about BC, or forced migration; I'm talking about the effort required when you *do* want the new facilities.
>
> It's not at all clear to me where an existing library would add "export" and "import" keywords in order to mark some classes as internal. It would seem to require completely rearranging the project structure, and rewriting references to all the moved parts.
>
> Compare to the work required to use a "namespace visibility" feature:
>
> 1. Add a keyword, or an attribute, at the top of each internal class, limiting its use to a certain namespace prefix.
> 2. There is no step 2. Nothing needs to be moved, or renamed, or accessed differently.
>
> That's what I mean by starting with how PHP is used today, and finding ways to enhance it.


Creating a namespace doesn't ensure your structures will be internal. In 
PHP, there's nothing today that allow it. Creating a "Package" system 
that allows internal private structures is not yet on the menu (I have 
other ideas for that, in another thread), but starting with the concept 
of PHP modules as files starts the path to make sure a PHP package can 
be bundled as a list of PHP modules operating with each other, but the 
package configuration is the only one that can `export` the public API, 
and all the rest of the defined structures are private and only visible 
from inside the package.
The goal of modules, with  the current problems that it solves, makes it 
really straightforward to follow with packages, and therefore, "private 
code". (in userland, because it's another thing for the engine and 
compiler...).

Such things would allow two similar versions to coexist, and we could 
even imagine two PHP packages having the same name: since they have to 
be modules, it's not like defining two classes in the same namespaces, 
because every module will have an internal hashed prefix, and their own 
personal module tree.


Modules solve the very first part of packages, and solve a few issues 
with allowing the creation of private code, even though the compromise 
is, for now, that such private code is only possible from inside a module.

---

## #131197 — Rowan Tommins [IMSoP] — 2026-06-10T15:24:10+00:00

URL: https://externals.io/message/131171#131197 · In reply to: #131196

>Le 10/06/2026 à 12:23, Rowan Tommins [IMSoP] a écrit :
> > […]
>
>
>Creating a namespace doesn't ensure your structures will be internal. In PHP, there's nothing today that allow it.


You've missed the point again. I'm not saying that namespaces allow this today. I'm saying that any attempt to solve problems around packages in PHP should start with the functionality we already have, and the way the language is actually used.

Take Guzzle, for example; it has 43 source files within a specific namespace root. A handful of those are marked "@internal", and a way for PHP to error if users reference them directly would be useful. Some users end up wanting conflicting versions of Guzzle simultaneously, e.g. in different WordPress plugins; so some way of isolating or rewriting class names (and all their references) would be useful.

There are not 43 separate "modules", and the maintainers of Guzzle aren't going to combine all of them into one file. Defining a single package with multiple files is not a stretch goal, it's the only plausible starting point. 

(And before anyone starts whining about Composer and PSR-4, I could find you a PEAR package from 25 years ago and make the same point. Single file packages have *never* been the norm in PHP.)


Rowan Tommins
[IMSoP]

---

## #131199 — Morgan — 2026-06-11T00:23:29+00:00

URL: https://externals.io/message/131171#131199 · In reply to: #131197

> 
> Take Guzzle, for example; it has 43 source files within a specific namespace root. A handful of those are marked "@internal", and a way for PHP to error if users reference them directly would be useful. Some users end up wanting conflicting versions of Guzzle simultaneously, e.g. in different WordPress plugins; so some way of isolating or rewriting class names (and all their references) would be useful.
> 
> There are not 43 separate "modules", and the maintainers of Guzzle aren't going to combine all of them into one file. Defining a single package with multiple files is not a stretch goal, it's the only plausible starting point.
> 
Just throwing this out, but couldn't the "module file" - the one that 
users bring into their project and the one that exports what it's 
declared to export - delegate the definitions of the functions etc. both 
public and private to other files?

I mean, PHP already _has_ an "include" statement...

---

## #131200 — Rowan Tommins [IMSoP] — 2026-06-11T06:52:40+00:00

URL: https://externals.io/message/131171#131200 · In reply to: #131199

>
>
> > […]
>Just throwing this out, but couldn't the "module file" - the one that users bring into their project and the one that exports what it's declared to export - delegate the definitions of the functions etc. both public and private to other files?
>
>I mean, PHP already _has_ an "include" statement...


It could, but that just moves the problem: right now, I can use any class in Guzzle from anywhere in my application as long as I configure the right autoloader. I can pass objects from Guzzle into and out of other libraries, and they all agree on what each class name means.

Having to write an "import" statement means I have to be conscious of where I "start" using Guzzle. If every import of the library creates a magically prefixed copy of the entire library, I'm not even clear how I'd refer to different Guzzle classes in different files of my application; let alone how libraries could pass those objects in and out and agree that they were the same class.

Namespace visibility is really really easy; as if file visibility, if you want that. Usable namespace rewriting is much more difficult, but doing it per file makes it harder, not easier.

As Larry said, just stop trying to make file==module work. It's not the right approach for PHP.


Rowan Tommins
[IMSoP]

---

## #131201 — Alex Rock — 2026-06-11T08:12:52+00:00

URL: https://externals.io/message/131171#131201 · In reply to: #131200

Le 11/06/2026 à 08:52, Rowan Tommins [IMSoP] a écrit :
> 
> > […]
>
> It could, but that just moves the problem: right now, I can use any class in Guzzle from anywhere in my application as long as I configure the right autoloader. I can pass objects from Guzzle into and out of other libraries, and they all agree on what each class name means.
>
> Having to write an "import" statement means I have to be conscious of where I "start" using Guzzle. If every import of the library creates a magically prefixed copy of the entire library, I'm not even clear how I'd refer to different Guzzle classes in different files of my application; let alone how libraries could pass those objects in and out and agree that they were the same class.
>
> Namespace visibility is really really easy; as if file visibility, if you want that. Usable namespace rewriting is much more difficult, but doing it per file makes it harder, not easier.

Namespace visibility could be implemented, but if you want to have some 
public code that uses "include" to get access to some other private code 
in the same namespace, or sub-namespace, then you would somehow hav to 
specify to whom this namespace becomes visible. Extending the module 
concept into "packages" makes sure that a package could have an 
entrypoint that is publicly accessible, and all files in said package 
would have to specify which package they are part of. But by design, 
namespaces aren't dynamic, neither conditional (unless you define one 
class, then register lots of aliases of this class in different 
namespaces), so namespace visibility would still be easily "hackable" 
without having to touch PHP's internals (like from using Reflection). 
It's similar to namespace spoofing where you redefine a function in a 
certain namespace just for the sake of overriding native behavior. 
That's why some CS fixers enforce adding "use function" or add the "\" 
root namespace to function calls, to prevent from overriding native 
functions. So far, I don't see how namespace/file visibility would help 
if you don't have a bi-directional definition system. That's usually 
what Packages are for: they define their public entrypoints, and all the 
rest is internal to the package and supposedly inaccessible from the 
global scope.

Another potential workaround would be "Friend classes" (and there are 
lots of discussions lately about it), but this system only works for 
classes and cannot be applied to other structure types. It's not a bad 
idea, but this enforces "hiding" all your internal logic in classes. 
Constants would have to be internal static class constants, functions 
would be internal static class functions, and so on. As said, it's not a 
bad idea, but IMO this is a "workaround with PHP's existing tools", and 
doesn't improve the language, it just improves the OOP part. And 
wouldn't fix the "multiple versions of same package" issue either.

> As Larry said, just stop trying to make file==module work. It's not the right approach for PHP.
If you want to make namespace==module instead, this could maybe be a 
nice approach for maintainers in making "some private code" in their 
packages, but it's not enough:
- It still allows creating a namespace from anywhere else in order to 
"hack" into it and publicly expose an API that wasn't supposed to be 
exposed at all.
- It doesn't solve the issue of having the same package in different 
versions, since namespaces will be the same

The file==module approach is imperfect, indeed. In Rust, you can create 
tons of modules in one single file, each with their own private and 
public code, but most of the times, developers create one file per 
module, and the module's name is the filename itself. A Rust Crate (aka 
"package") goes even further in allowing a module to expose its API 
publicly only for the package. I think this is the best approach, and 
it's safer than how JS modules are implemented. However, considering the 
dynamic (instead of compile-only) nature of PHP, and considering the 
ecosystem's occasional needs to have multiple versions for one package, 
the addition of an internal hashed prefix for each module makes it 
possible. It reuses everything that PHP already has, and it can be 
almost transparent for the end-user: they won't write `use` but rather 
`import` instead, and maybe use a local name instead of a FQCN (though 
modules can define namespaces, therefore you can import a FCQN). And I 
still think that using namespaces for that, and namespace visibility, 
won't help, since they all register things in the global scope with the 
same names, and doesn't fix the other issues.

---

## #131202 — Rowan Tommins [IMSoP] — 2026-06-11T09:54:06+00:00

URL: https://externals.io/message/131171#131202 · In reply to: #131201

>
>Namespace visibility could be implemented, but if you want to have some public code that uses "include" to get access to some other private code in the same namespace, or sub-namespace, then you would somehow hav to specify to whom this namespace becomes visible.


Why would you need that?


> namespace visibility would still be easily "hackable" without having to touch PHP's internals (like from using Reflection).


Visibility isn't a security tool, it's a helper to stop people making mistakes.


> That's why some CS fixers enforce adding "use function" or add the "\" root namespace to function calls, to prevent from overriding native functions.


I believe that's actually about performance: if you don't specify, the engine has to try the lookup on every call, in case a namespaced function was registered.


> So far, I don't see how namespace/file visibility would help if you don't have a bi-directional definition system. 


It's really, really simple: you mark the internal parts "internal". Then you use them from all the same places they're intended, inside the package's namespace; and people using them outside those intended places get a helpful error message that they're using the library wrong.

It's possible we also want to add a way to "seal" the namespace against new definitions anyway, to help with optimisation. Namespaces, not files, would still be the right starting point for that.



>- It still allows creating a namespace from anywhere else in order to "hack" into it and publicly expose an API that wasn't supposed to be exposed at all.


Non-issue: as above, visibility is not a security tool.



>- It doesn't solve the issue of having the same package in different versions, since namespaces will be the same


A solution to that which requires hundreds of thousands of libraries to rewrite all their code to use relative references rather than namespaced global names isn't going to get very far.

I'll say it one more time, then stop: any solution needs to start with what we have, and what we have is namespaces.




Rowan Tommins
[IMSoP]

---

## #131210 — Alex Rock — 2026-06-12T12:37:18+00:00

URL: https://externals.io/message/131171#131210 · In reply to: #131202

Le 11/06/2026 à 11:54, Rowan Tommins [IMSoP] a écrit :
> 
> > […]
>
> Why would you need that?
>
>
> > […]
>
> Visibility isn't a security tool, it's a helper to stop people making mistakes.
>
>
> > […]
>
> I believe that's actually about performance: if you don't specify, the engine has to try the lookup on every call, in case a namespaced function was registered.
>
>
> > […]
>
> It's really, really simple: you mark the internal parts "internal". Then you use them from all the same places they're intended, inside the package's namespace; and people using them outside those intended places get a helpful error message that they're using the library wrong.
>
> It's possible we also want to add a way to "seal" the namespace against new definitions anyway, to help with optimisation. Namespaces, not files, would still be the right starting point for that.


What if we have this:

<?php

// vendor/my-library/src/MyClass.php

namespace MyNamespace;

internal class MyClass {};

Fine code would look like this:

<?php

// index.php

function main() {
	$o = new \MyNamespace\MyClass(); // Fatal Error: cannot create instance of internal class MyNamespace\MyClass
  outside of the MyNamespace namespace
}

And "not fine code" would look like this:

<?php

// index.php

namespace MyNamespace {
	function createObject() {
		return new \MyNamespace\MyClass();
	}
}

namespace {
	function main() {
		$o = \MyNamespace\MyHack::createObject(); // Works.
	}
}

Your "internal" system would be quite useless, because anyone would be 
able to do it. It can even be done at the autoloader level if one 
wanted. Doesn't use Reflection or such.

Then, it leads to your first question:

> 
> > […]
> Why would you need that?

Well, to solve the previously shown issue.

The "internal class" previously set (or moved to "internal namespace": 
same issue) is useless, and it would kinda do the same thing as /** 
@internal */ today: cannot be enforced at runtime, so not safe at all.

There's no need to add "internal class" if this system doesn't 
internalize anything and is easily "hackable" from outside.


If you want to have *actual* internal code, there are only two ways to 
achieve this:

- have code that's internal on said file, with the proposed "module" 
system. Sure, it's only for a file, but it's better than nothing
- have a "package" system so that a package has an entrypoint that 
defines private and public files, and said files expose which "package"

I repeat: since PHP is hugely flexible, especially via the whole 
Reflection API, I don't mean to "make hacking impossible", because, 
well, with PHP you can override everything. I mean to "make it as hard 
and ugly as possible". Sure you can execute a private method in a 
class/object by using either Reflection (or a bounded closure), but 
these are explicit hacks.
With "just namespaces", you can just use the widely used autoload system 
and create a class somewhere with a static method, or a global function 
(like above) , it doesn't even look like a hack.


> > […]
>
> Non-issue: as above, visibility is not a security tool.

If bad practices are easily accessible from a new feature, you will 
expect lots of bad practices, because that's what lots of coders end up 
doing. We all know that, and if you don't know it, I hope you'll realize 
it some day. That's why "enforcing good practices" is a better approach, 
because it doesn't disallow bad code, it just makes it harder to 
produce, costing more energy, time, money, and technical debt.

> > […]
>
> A solution to that which requires hundreds of thousands of libraries to rewrite all their code to use relative references rather than namespaced global names isn't going to get very far.
>
> I'll say it one more time, then stop: any solution needs to start with what we have, and what we have is namespaces.

In that case, let's move directly to "packages" without talking about 
"modules" at all, and make namespaces package-aware somehow. Or the 
contrary: make a package implicitly create "protected" namespaces.

And use it like this for example:

<?php // vendor/some-package/package.php

define package MyPackage;

package_include MyPackage\MyClass from __DIR__.'/src/MyClass.php';

// ... and possibly all other includes
// (note: this is an idea, there might be another way.
// It could even be an spl_register_internal() function or anything similar
//   that makes sure it's not possible to define package-bounded structures
//   from outside the package definition file



<?php // vendor/some-package/src/MyClass.php

package MyPackage; // implies "namespace MyPackage;" too, to globally reference non-internal structures

internal class MyClass {};



<?php // invalid include

include 'vendor/some-package/src/MyClass.php';  // Fatal error: package "MyPackage" was not defined.



<?php // invalid instantiation

include 'vendor/some-package/src/package.php';
include 'vendor/some-package/src/MyClass.php'; // Just an appendage to the MyPackage, could even be autoloaded

new MyPackage\MyClass(); // Fatal error: cannot create instance of internal class MyClass


This way, once a `define package MyPackage` has been called, any further 
file doing `package MyPackage` will throw an error


The "Package" thing is a bit more recommended, but IMO it needs a 
bidirectional system.


Though, the concept of "modules" as I explained it, whether it uses 
namespaces or not, makes "package-alike" systems possible, even if it's 
in one single file.

Another "workaround" to define structures each in their file and merging 
them together in a module would be a "merging script" system, but that's 
ugly, and it is similar to the "compilation/transpilation" process in 
Node.js, where your publicly available package is a "built" version of 
your source code (what happens in NPM packages), rather than the source 
code itself (like in PHP or Rust).


I think I won't convice you of anything, and so far there are not many 
people participating in this discussion, so feel free to drop out if 
it's annoying or boring for you, I won't take it personally :)

---

## #131211 — Rowan Tommins [IMSoP] — 2026-06-12T13:55:47+00:00

URL: https://externals.io/message/131171#131211 · In reply to: #131210

>Your "internal" system would be quite useless, because anyone would be able to do it. It can even be done at the autoloader level if one wanted. Doesn't use Reflection or such.


I honestly don't think that's a problem. If someone writes code that deliberately works around an "internal" marker, they should not be surprised when that code breaks on library updates.

Right now, there is a package which dynamically removes the "final" keyword from every library installed. Does that mean "final" is useless? I don't think so.

Such "escape hatches" often have legitimate uses, e.g. when writing unit tests. But see below about "sealing"...


> it would kinda do the same thing as /**
@internal */ today: cannot be enforced at runtime, so not safe at all.

The difference is that an "@internal" marker can be *accidentally* ignored: a user can see the class mentioned, and assume they can use it anywhere in their own code.


> have code that's internal on said file, with the proposed "module"
system. Sure, it's only for a file, but it's better than nothing

You don't need a new type of file for this, you can just add a "file-private" keyword. The only difference from adding an "export" keyword is that definitions would be public by default, in the same way methods in a class are public by default.


>This way, once a `define package MyPackage` has been called, any further file doing `package MyPackage` will throw an error


Yes, this is the general idea behind my remark about "sealing" a namespace: some way to say "all symbols in this namespace are loaded, do not allow more to be added". As well as stronger enforcement of boundaries, that would allow OpCache to do "link-time" optimisations and multifile caching.

It doesn't solve the "containerisation" problem (running multiple copies of the same library in different contexts), but I think that needs to be at a wider scale still: if you run two versions of Guzzle, you need to two versions of PSR interfaces to go with them, and so on. I think it needs some way for the *consumer* of the code (e.g. a WordPress plugin) to define what's "inside" and "outside" the container.

I don't think it's actually necessary (or useful) for a "container" system to depend on a "package" system at all - the ideal is to say "all files included in this directory are inside the container", without any cooperation from the author of those files.

So I think both "packages" (a way for the author of Guzzle to define a visibility and optimisation boundary) and "containers" (a way for a WordPress plugin to load a whole vendor directory without causing name conflicts) are useful ideas to discuss, but trying to talk about both at once leads to a lot of muddle.


Rowan Tommins
[IMSoP]

---

## #131215 — Larry Garfield — 2026-06-12T16:54:16+00:00

URL: https://externals.io/message/131171#131215 · In reply to: #131210

> What if we have this:
>
> <?php
>
> // vendor/my-library/src/MyClass.php
>
> namespace MyNamespace;
>
> internal class MyClass {};
>
> Fine code would look like this:
>
> <?php
>
> // index.php
>
> function main() {
> 	$o = new \MyNamespace\MyClass(); // Fatal Error: cannot create 
> instance of internal class MyNamespace\MyClass
>  outside of the MyNamespace namespace
> }
>
> And "not fine code" would look like this:
>
> <?php
>
> // index.php
>
> namespace MyNamespace {
> 	function createObject() {
> 		return new \MyNamespace\MyClass();
> 	}
> }
>
> namespace {
> 	function main() {
> 		$o = \MyNamespace\MyHack::createObject(); // Works.
> 	}
> }
>
> Your "internal" system would be quite useless, because anyone would be 
> able to do it. It can even be done at the autoloader level if one 
> wanted. Doesn't use Reflection or such.

But it would allow a workaround to access a private class for testing.  (And no, "it's private so you can't test it" is not the answer.  Classes may be black boxes, but for testing purposes, modules are not and should not be.  And in PHP, tests cannot be in the same file as the thing being tested.)

> Then, it leads to your first question:
>
> > […]
> Well, to solve the previously shown issue.
>
> The "internal class" previously set (or moved to "internal namespace": 
> same issue) is useless, and it would kinda do the same thing as /** 
> @internal */ today: cannot be enforced at runtime, so not safe at all.
>
> There's no need to add "internal class" if this system doesn't 
> internalize anything and is easily "hackable" from outside.
>
>
>
> If you want to have *actual* internal code, there are only two ways to 
> achieve this:

The biggest problem with discussions about modules in PHP is that there is no consensus on what problem we're even trying to solve.  Visibility?  Double-loading?  Non-PSR-4 file organization?  Performance?  Enforcing PSR-1 "just declare symbols" rules?  

Ask 10 people on this list and you'll get 12 different combinations of desired features.  Trying to solve the How before we agree on the What is why we keep going in circles every year or so.

(This would be an excellent use case for a chartered Working Group, as Ben has proposed.)

> - have code that's internal on said file, with the proposed "module" 
> system. Sure, it's only for a file, but it's better than nothing
> - have a "package" system so that a package has an entrypoint that 
> defines private and public files, and said files expose which "package"

I would strongly urge everyone to keep to the terminology proposed here:

https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md

"Package" already means "thing you download via composer."  It's a unit of distribution.  Trying to redefine it to mean "a unit of scope" is just going to create confusion.

> > […]
> In that case, let's move directly to "packages" without talking about 
> "modules" at all, and make namespaces package-aware somehow. Or the 
> contrary: make a package implicitly create "protected" namespaces.

Please see the link above, in detail.  Arnaud and I went very far down this rabbit hole.  Make sure you've seen the prior art before you try to go down it again.

One other thing I'll note: In today's autoload-based PHP, *you do not know the path to a given class*.  That is by design.  There is no rule that composer won't reorganize the contents of vendor at some point.  Hard coding a path into a file to get from that file to some other file in the file tree is a PHP4-ism we should leave to that era.  So module inclusion statements that require knowing the path from one file to another is an immediate dead end.

--Larry Garfield

---

## #131219 — Michael Morris — 2026-06-12T17:25:41+00:00

URL: https://externals.io/message/131171#131219 · In reply to: #131215

On Fri, Jun 12, 2026 at 12:54 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> 
>
> > […]
>
> But it would allow a workaround to access a private class for testing.
> (And no, "it's private so you can't test it" is not the answer.  Classes
> may be black boxes, but for testing purposes, modules are not and should
> not be.  And in PHP, tests cannot be in the same file as the thing being
> tested.)
>
> > […]
> some public code that uses "include" to get access to some other private
> code in the same namespace, or sub-namespace, then you would somehow hav to
> specify to whom this namespace becomes visible.
> > […]
>
> The biggest problem with discussions about modules in PHP is that there is
> no consensus on what problem we're even trying to solve.  Visibility?
> Double-loading?  Non-PSR-4 file organization?  Performance?  Enforcing
> PSR-1 "just declare symbols" rules?
>
> Ask 10 people on this list and you'll get 12 different combinations of
> desired features.  Trying to solve the How before we agree on the What is
> why we keep going in circles every year or so.
>
> (This would be an excellent use case for a chartered Working Group, as Ben
> has proposed.)
>
> > […]
>
> I would strongly urge everyone to keep to the terminology proposed here:
>
> https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md
>
> "Package" already means "thing you download via composer."  It's a unit of
> distribution.  Trying to redefine it to mean "a unit of scope" is just
> going to create confusion.
>
> > […]
> what we have, and what we have is namespaces.
> > […]
>
> Please see the link above, in detail.  Arnaud and I went very far down
> this rabbit hole.  Make sure you've seen the prior art before you try to go
> down it again.
>
> One other thing I'll note: In today's autoload-based PHP, *you do not know
> the path to a given class*.  That is by design.  There is no rule that
> composer won't reorganize the contents of vendor at some point.  Hard
> coding a path into a file to get from that file to some other file in the
> file tree is a PHP4-ism we should leave to that era.  So module inclusion
> statements that require knowing the path from one file to another is an
> immediate dead end.
>
> --Larry Garfield


Points taken. Rounding back to what I remember of last year’s conversation
with Rowain, what about containers?

Requiring a container file gets you an interface to the container living in
that file. The code execution occurs on a different thread entirely, so
calls to and from it would ideally be asynchronous.

Function or process based containers should be doable from userland
using shell_exec function (set aside a moment the security implications)
with the caveat that the whole process has to run on any call. Illustrative
of concept, but hardly production ready.

A more ideal case is for the container to return an interface that hosts
multiple methods and persists until its parent process terminates.

With some work containers might also be able to step over initialization
and config processes if a container is allowed to persist between page
requests.

Key idea here is this - put this stuff on its own process thread. That
doesn’t stop people from requiring files within the container directly.
Containerizing existing libraries involves writing the wrapper.

---

## #131225 — Alex \"Pierstoval\" Rock — 2026-06-13T10:31:43+00:00

URL: https://externals.io/message/131171#131225 · In reply to: #131219

On Fri, Jun 12, 2026 at 12:54 PM Larry Garfield <larry@garfieldtech.com> 
wrote:
 > I would strongly urge everyone to keep to the terminology proposed here:
 >
 > https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md

Done, that's a VERY interesting resource, thanks! I see that some of my 
ideas are close to what you brainstormed, and I have another point of 
view of how it can be implemented :)
Using an INI file definitely makes sense if it needs these three keys to 
be defined somehow.
I'll try to reuse your wording for clarity and consistency :)


Le 12/06/2026 à 19:25, Michael Morris a écrit :
>
>
> On Fri, Jun 12, 2026 at 12:54 PM Larry Garfield 
> <larry@garfieldtech.com> wrote:
>
>     
>
> > […]
>     would be
> > […]
>
>     But it would allow a workaround to access a private class for
>     testing.  (And no, "it's private so you can't test it" is not the
>     answer.  Classes may be black boxes, but for testing purposes,
>     modules are not and should not be.  And in PHP, tests cannot be in
>     the same file as the thing being tested.)
>
One of the goals of modules is to encapsulate more elements of a module 
or package than just class methods, therefore private/internal elements 
of a module should not be accessible from outside, that's exactly the 
point of it. So yeah, "it's private so you can't test it" still has to 
apply, as said, just like you shouldn't test your private class methods 
(even though PHP allows it with hacks).


>
> > […]
>     wrote:
> > […]
>     have some public code that uses "include" to get access to some
>     other private code in the same namespace, or sub-namespace, then
>     you would somehow hav to specify to whom this namespace becomes
>     visible.
> > […]
>     namespace":
> > […]
>     at all.
> > […]
>     ways to
> > […]
>
>     The biggest problem with discussions about modules in PHP is that
>     there is no consensus on what problem we're even trying to solve. 
>     Visibility?  Double-loading?  Non-PSR-4 file organization? 
>     Performance?  Enforcing PSR-1 "just declare symbols" rules?
>
On my side:

1. Allowing internal code units, not exposed to the outside of a package 
or module, but accessible from within it.
    This is for maintainers to have a much easier way to define 
non-BC-covered code.
2. PSR-1 "just declare symbols" is, indeed, something I personally would 
like to happen. But technically, this is the "first step" of my initial 
thread, the one with "definition files", and it's really easy to do (I 
have a branch on php-src for that). This is to ensure loading a PHP file 
has zero side-effect after being compiled.
3. Allowing multiple modules with same names to be loaded at the same 
time, as long as they are different files.

If these are problems that a sufficiently enough portion of the 
community would like to solve, then I think we should go for it somehow :)


Your implementation proposal makes everything internal by default, AND 
allows using a specific keyword to expose parts of a structure, or the 
whole structure, to the outside world. This is partly similar to what I 
already have in mind for what I called "packages", because I pictured 
"module" as being a single file and its module-inclusion tree (similar 
to Rust crates, and Node.js local-file-imported modules)

What's missing, for now, is a method to allow two modules of same names 
to coexist.

>     One other thing I'll note: In today's autoload-based PHP, *you do
>     not know the path to a given class*.  That is by design. There is
>     no rule that composer won't reorganize the contents of vendor at
>     some point.  Hard coding a path into a file to get from that file
>     to some other file in the file tree is a PHP4-ism we should leave
>     to that era.  So module inclusion statements that require knowing
>     the path from one file to another is an immediate dead end.
>
Seems like a dead-end based on all counter-arguments I've been having 
since I started the discussion, so I'll have to follow this movement I 
guess :D



> Points taken. Rounding back to what I remember of last year’s 
> conversation with Rowain, what about containers?
>
> Requiring a container file gets you an interface to the container 
> living in that file. The code execution occurs on a different thread 
> entirely, so calls to and from it would ideally be asynchronous.
>
> Function or process based containers should be doable from userland 
> using shell_exec function (set aside a moment the security 
> implications) with the caveat that the whole process has to run on any 
> call. Illustrative of concept, but hardly production ready.
>
> A more ideal case is for the container to return an interface that 
> hosts multiple methods and persists until its parent process terminates.
>
> With some work containers might also be able to step over 
> initialization and config processes if a container is allowed to 
> persist between page requests.
>
> Key idea here is this - put this stuff on its own process thread. That 
> doesn’t stop people from requiring files within the container 
> directly. Containerizing existing libraries involves writing the wrapper.

Containers as "code block that's executed in another thread" seems like 
something completely different than implementing modules, or am I 
missing something?

---

## #131226 — Michael Morris — 2026-06-13T15:32:08+00:00

URL: https://externals.io/message/131171#131226 · In reply to: #131225

On Sat, Jun 13, 2026 at 6:32 AM Alex "Pierstoval" Rock <pierstoval@gmail.com>
wrote:

>
> Containers as "code block that's executed in another thread" seems like
> something completely different than implementing modules, or am I missing
> something?
>

It gives you what you're looking for without having to overhaul the
language in any massive way. I made the prior post on my phone where it's
hard to make a message, but I'm on my computer this morning so I can
elaborate now. And while elaborating, I realized this probably needs it's
own discussion thread entirely because it can solve not just the original
plugin problem, but also the process workers problem if handled correctly.

Let's review.

PHP's ecosystem has packages already. It doesn't need modules as seen in
JavaScript. What it needs in certain corner cases is better package
isolation: The ability for a package to do its thing without disturbing or
being disturbed by outside code. Drupal, Laravel, Symfony, et al do a great
job of isolating their libraries, but they still have to register their
class names and constants on the global name table. Under the hood
Namespaces in PHP are a bit of a hack - they prepend the stated namespace
to all labels declared in the file, and the use statement allows the fully
qualified name of a class, function or constant to be stated only once,
usually at the head of the file. It also allows aliasing, say if you have a
"Parser" class out of two different packages - you can alias them as
"FooParser" and "BarParser". It looks like Java or JavaScript importing,
but it isn't.

The global namespace of PHP, both for variables and constants, is limited
to the current thread.  PHP isn't natively multithreaded - there's a whole
other RFC on async/await in progress. What I'm about to go over would
benefit from that work, but it isn't a prerequisite to implementing
containers.

A container is an interface to a PHP process on another thread. It takes
the form of an object that is returned by the included file. Where returns
are optional for include/require, the new statement require_container
mandates that an object be returned. I'd go even further and mandate that
the object must implement a ContainerInterface that is added to PHP core.

By virtue of running on its own thread a container has its own symbol table
both for constants/classes/functions but also for variables. It is further
restricted by having no superglobals - no $_REQUIRE, $_POST, $_GET, etc. If
it needs to work with these then they'll have to come in through the
interface from the outside code, or perhaps a PHP internal function can
supply them with their own copy of these variables, but crucially any
modification they make will not affect other containers.

When PHP starts a container, it will cache the returned code and hand over
a copy. This differs slightly from normal OP_Cache which caches the
bytecode of the file itself so that new parsing isn't needed. It was at
this moment I realized that two problems, not one, get solved by this.
Let's start with the original though, package isolation. We'll use Guzzle
for an example. The calling code

<?php
$guzzle = require_container('path/to/guzzle/container/class')
?>

Now in PHP we've tried to get away from having to reference specific paths
like this. Coming up with ways to resolve that path can be done in userland
just as composer does this in userland. One example might look like this:

<?php
$guzzle = require_container(Container::getPath('Guzzle'))
?>

The required file might look like this, at least in part

<?php
use GuzzleHttp\Client;

require 'path/to/my/package/composer/autoloader';

return new GuzzleContainer implements Container {
  public static getClient($options) {
    return new Client($options);
  }
}
?>

And I trust the folk who know how the engine works can clearly see if this
has a hope in Hell of working or not. I'm going to stop here for their
input.  If it does though, here's the other problem that might be solved.
Here, we look at Drupal's existing entry index.php

<?php

use Drupal\Core\DrupalKernel;
require_once 'autoload_runtime.php';
return static function () {  return new DrupalKernel('prod', require
'autoload.php');};

?>


Without digging deeply, suffice to say that "new DrupalKernel" call kicks
off a lot of config processing that has to be done before the request can
even start to be processed, and this is redundant to every request. Quite a
bit of caching is in play to reduce this burden as much as possible, but if
we have a container system, we can fetch a pre-built worker and avoid all
of it.

<?php

(require_container 'drupal_worker.php')->parse($_GET, $_POST, $FILES,
$_SERVER, $_SESSION);

?>

Remembering what I mentioned before, that the PHP Engine caches the
worker's state at the moment it is returned to the calling file. So it can
get its own autoloader setup and configuration parsing completely done and
be in a ready state to handle the request. It would likely require some
serious refactoring to fully take advantage of this, but the performance
gains should be very real.

Thoughts?

---

## #131227 — Rowan Tommins [IMSoP] — 2026-06-13T15:46:17+00:00

URL: https://externals.io/message/131171#131227 · In reply to: #131225

In my opinion this ...

> So yeah, "it's private so you can't test it" still has to apply, as said, just like you shouldn't test your private class methods (even though PHP allows it with hacks).

... doesn't match this:

>1. Allowing internal code units, not exposed to the outside of a package or module, but accessible from within it.
>   This is for maintainers to have a much easier way to define non-BC-covered code.

Just because a piece of functionality is internal to the library, doesn't mean it shouldn't be covered by unit tests. Indeed that's almost the definition of a unit test vs an integration test, if the library only exposes a narrow interface; and "refactor into a separate helper" is a common answer to "I want to unit test a private method".

Meanwhile this ...

>3. Allowing multiple modules with same names to be loaded at the same time, as long as they are different files.

... basically answers this:

>Containers as "code block that's executed in another thread" seems like something completely different than implementing modules, or am I missing something?

In order to reuse code written assuming a class name is unique (which is every line of PHP currently in existence) you need to define a boundary where code "inside" runs without affecting the code "outside", except through a specific interface. Whether that would literally be two separate threads, I'm not sure, but it certainly has a lot in common.

And yes, that is completely different from "modules" as units of scope. It needs to affect the *public* parts of a module, as well as the *private* parts; and it needs to affect *a group of modules* which rely on each other, not each in isolation.



Rowan Tommins
[IMSoP]

---

## #131229 — Rowan Tommins [IMSoP] — 2026-06-13T17:14:18+00:00

URL: https://externals.io/message/131171#131229 · In reply to: #131226

>Namespaces in PHP are a bit of a hack - they prepend the stated namespace
>to all labels declared in the file, and the use statement allows the fully
>qualified name of a class, function or constant to be stated only once,
>usually at the head of the file. It also allows aliasing, say if you have a
>"Parser" class out of two different packages - you can alias them as
>"FooParser" and "BarParser". It looks like Java or JavaScript importing,
>but it isn't.

A small correction: this is exactly the same way that namespaces work in Java, with exactly the same implication: the fully qualified class name has to be globally unique. Java's solution to isolating an entire set of names is apparently to define an "Isolated ClassLoader": https://www.javathinking.com/blog/what-is-an-isolated-classloader-in-java/

C# also has this model of namespaces, but allows you to isolate a set of names from a particular "assembly" using an "extern alias" statement: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extern-alias

I suspect both mechanisms rely on details of their respective runtimes that wouldn't translate to PHP, but in principle what we're looking for is something similar.



Rowan Tommins
[IMSoP]

---

## #131267 — Alex Rock — 2026-06-15T09:45:18+00:00

URL: https://externals.io/message/131171#131267 · In reply to: #131229

Le 13/06/2026 à 19:14, Rowan Tommins [IMSoP] a écrit :
> 
>
> > […]
> A small correction: this is exactly the same way that namespaces work in Java, with exactly the same implication: the fully qualified class name has to be globally unique. Java's solution to isolating an entire set of names is apparently to define an "Isolated ClassLoader": https://www.javathinking.com/blog/what-is-an-isolated-classloader-in-java/
>
> C# also has this model of namespaces, but allows you to isolate a set of names from a particular "assembly" using an "extern alias" statement: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extern-alias
>
> I suspect both mechanisms rely on details of their respective runtimes that wouldn't translate to PHP, but in principle what we're looking for is something similar.
>
>
>
> Rowan Tommins
> [IMSoP]


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...).
After 18 years of PHP coding, including 15 years as a professional, I've 
seen a huge lot of legacy PHP projects that benefit the language's 
flexibility. Sure thing many of businesses could succeed thanks to being 
able to override almost everything (contrary to stricter languages), but 
maintaining these codebases with up-to-date versions of PHP, or 
3rd-party libs, is so much of a hassle that, at some point, lots of 
maintainers are going more and more into adding new things into PHP that 
enforce a bit more strictness, opt-in of course. Some other libs (I'm 
thinking about EasyAdmin, in the Symfony ecosystem, for example) have 
even made the choice to close pretty much everything and provide 
"maintainable" public entrypoints, enforcing the Open/Close principle 
with a strict approach (immutable final classes, stricter options and 
arguments, etc.). It has its pros and cons, and can be frustrating for 
some devs, but the main goal was to make maintenance a bit easier for 
the core devs so that they are not forced to maintain BC on too many layers.

Introducing Modules, whether they are based on namespaces or not, would 
help maintainers, I think, and could reinvent how we think of PHP 
packages, while still keeping PHP what it has always been because it 
would be opt-in for maintainers.

On my side, I don't care if modules are tightly coupled to namespaces or 
not, and if more, that's even why my first idea was to bind a module to 
a single-file at first, and making all modules imported from such file 
being non-importable-again, thus creating a single module tree that can 
be stored at compile-time, have no side-effect at load-time (which is a 
big safeguard to avoid other issues), and allowing multiple modules of 
seemingly same name to be loaded in the memory at the same time, 
similarly to how Node.js allows having different versions of the same 
packages to be installed, while also allowing to have one version used 
by many modules too.

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 still hope we could have a grouped discussion on this.

Larry's Modules proposal (here: 
https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md ) 
is IMO a very good starting point, and it only lacks the "multiple 
modules with different versions". And module definition with an INI file 
looks interesting.

Adding a "container" system when importing a module can maybe allow 
this, I don't know...?

@Michael: do you think you could start a new thread for PHP containers? 
Do you already have some research that you could bring for a pre-RFC 
discussion?

---

## #131312 — Michael Morris — 2026-06-16T04:45:01+00:00

URL: https://externals.io/message/131171#131312 · In reply to: #131267

> Le 13/06/2026 à 19:14, Rowan Tommins [IMSoP] a écrit :
> > […]
> namespace
> > […]
> fully
> > […]
> have a
> > […]
> Java, with exactly the same implication: the fully qualified class name has
> to be globally unique. Java's solution to isolating an entire set of names
> is apparently to define an "Isolated ClassLoader":
> https://www.javathinking.com/blog/what-is-an-isolated-classloader-in-java/
> > […]
> names from a particular "assembly" using an "extern alias" statement:
> https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extern-alias
> > […]
> that wouldn't translate to PHP, but in principle what we're looking for is
> something similar.
> > […]
>
>
> 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.


> After 18 years of PHP coding, including 15 years as a professional, I've
> seen a huge lot of legacy PHP projects that benefit the language's
> flexibility. Sure thing many of businesses could succeed thanks to being
> able to override almost everything (contrary to stricter languages), but
> maintaining these codebases with up-to-date versions of PHP, or
> 3rd-party libs, is so much of a hassle that, at some point, lots of
> maintainers are going more and more into adding new things into PHP that
> enforce a bit more strictness, opt-in of course. Some other libs (I'm
> thinking about EasyAdmin, in the Symfony ecosystem, for example) have
> even made the choice to close pretty much everything and provide
> "maintainable" public entrypoints, enforcing the Open/Close principle
> with a strict approach (immutable final classes, stricter options and
> arguments, etc.). It has its pros and cons, and can be frustrating for
> some devs, but the main goal was to make maintenance a bit easier for
> the core devs so that they are not forced to maintain BC on too many
> layers.


> Introducing Modules, whether they are based on namespaces or not, would
> help maintainers, I think, and could reinvent how we think of PHP
> packages, while still keeping PHP what it has always been because it
> would be opt-in for maintainers.
>

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.

Any package/module/whatchamacallit system used in PHP needs to build on
this principle, not swim against it, if it is to have any hope of
consideration on the list.

Also, I think the following is important to point out - Composer is NOT a
package manager.  It is a dependency manager. There's a subtle difference
between the two.


>
> On my side, I don't care if modules are tightly coupled to namespaces or
> not, and if more, that's even why my first idea was to bind a module to
> a single-file at first, and making all modules imported from such file
> being non-importable-again, thus creating a single module tree that can
> be stored at compile-time, have no side-effect at load-time (which is a
> big safeguard to avoid other issues), and allowing multiple modules of
> seemingly same name to be loaded in the memory at the same time,
> similarly to how Node.js allows having different versions of the same
> packages to be installed, while also allowing to have one version used
> by many modules too.
>

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.

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.


> 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.


>
> I still hope we could have a grouped discussion on this.
>
> Larry's Modules proposal (here:
> https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md )
>
> is IMO a very good starting point, and it only lacks the "multiple
> modules with different versions". And module definition with an INI file
> looks interesting.
>
> Adding a "container" system when importing a module can maybe allow
> this, I don't know...?
>
> @Michael: do you think you could start a new thread for PHP containers?
> Do you already have some research that you could bring for a pre-RFC
> discussion?
>

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.

---

Next page: https://externals.io/message/131171.md?page=2
