Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127466 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id D3A651A00BC for ; Mon, 26 May 2025 19:39:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1748288246; bh=HT/xaGJ3pCd4zJYi32SuDp2MptzEr/Yg5E8HSe1BxxI=; h=Subject:From:In-Reply-To:Date:Cc:References:To:From; b=DZMeery7YxL5c2bYHFTWXN8Ml6Z2zK+pj75f0t210d9K5Wd6VIvrzBCTOKeJqUEi3 WblZBuhqYzopzKUx64xhL1WM+vVSvTcFl7smiIPsPSx2a5zTO7GFpaVP14uI45uHoN Vqfo/X4D7e9GBYxZNUbNE9ZAwBZbDSMt5VXlGNW+rUmzBOZMAC2vkEr3MtwVbCW3sv EQQrdUn8aTuBFlLf/EnYw4KPlv54kBKyzkoEBpM/7JSkMpWkWVr2dio5gNe9DraTUl 15iy81EDAPn63jfStx1K+fRf9E5+M6dCEluK/Hu58kn77D8SZCH1x2Gf5fNUGk0mjZ 4rjRd8WnmHBzA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 29E061801DC for ; Mon, 26 May 2025 19:37:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.2 required=5.0 tests=BAYES_20,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from outbound.qs.icloud.com (p-east3-cluster3-host11-snip4-2.eps.apple.com [57.103.86.105]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 26 May 2025 19:37:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=garsi.de; s=sig1; bh=jf7bWDkAFl98qm/sHp0GdLnRhgEr/eHKsa1YqJGZB/4=; h=Content-Type:Mime-Version:Subject:From:Date:Message-Id:To:x-icloud-hme; b=F42GMTJMAkbVX7pfMeWyv6Y5T4LiEU3AXbCvsPXsOtVmPI/eks2rXhpzI53667PKq uxrziotozUPkBdM/4MTiZVAUvqvQIkNct/WfP3v2j7Oc8k8DRos6gVLUPMQ+ie9JvO i4YM2CnBRw223A8whvadYshXsp67UxKSbDmhsnWkdaELqyfYdURvvpuPsRlJz11hWB ZfrHg1T0yaW9kFZEdHam/HjrAPH++r0PDfL+hmll0LvGu+zcIumiHnWAr5de34+ySM Wih4PWVCUEREYUh0T1b/6wRc6+dGKUOFMv9iq1ZA9wCB7RNX1q5BRXHhyx6u7Wp4A9 nY3jROtGb4gyA== Received: from outbound.qs.icloud.com (localhost [127.0.0.1]) by outbound.qs.icloud.com (Postfix) with ESMTPS id 563531800257; Mon, 26 May 2025 19:39:28 +0000 (UTC) Received: from smtpclient.apple (qs-asmtp-me-k8s.p00.prod.me.com [17.57.155.37]) by outbound.qs.icloud.com (Postfix) with ESMTPSA id ED09218007D3; Mon, 26 May 2025 19:39:27 +0000 (UTC) Content-Type: text/plain; charset=us-ascii Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.500.181.1.5\)) Subject: Re: [PHP-DEV] Module or Class Visibility, Season 2 In-Reply-To: <1c6dcd84-9016-48e1-971f-de7749cbdce8@rwec.co.uk> Date: Mon, 26 May 2025 21:39:15 +0200 Cc: PHP Internals Content-Transfer-Encoding: quoted-printable Message-ID: References: <9A26F72B-D0EF-414F-B193-BED3CAB26A0B@rwec.co.uk> <9f6a0d6e-27c3-4f77-aed6-e55147442b6f@app.fastmail.com> <673fd2db-b07f-439b-a4f2-e9519108d159@app.fastmail.com> <78641D8B-AF1D-4912-920A-D75A37C32F05@rwec.co.uk> <354cb888-97c4-4f8c-a0da-359d1e63c0f9@rwec.co.uk> <10D95B6E-094B-4EAE-A18A-AF6B795CB352@rwec.co.uk> <2adbff61-5e11-4d39-ab5c-d7950a4550a6@app.fastmail.com> <79E7FA26-2F5A-470C-B1DF-12CC46A08FE5@rwec.co.uk> <1c6dcd84-9016-48e1-971f-de7749cbdce8@rwec.co.uk> To: "Rowan Tommins [IMSoP]" X-Mailer: Apple Mail (2.3826.500.181.1.5) X-Proofpoint-GUID: a-wD56dfw038J64h3xp2ICgPHQQBRCeK X-Proofpoint-ORIG-GUID: a-wD56dfw038J64h3xp2ICgPHQQBRCeK X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1099,Hydra:6.0.736,FMLib:17.12.80.40 definitions=2025-05-26_10,2025-05-26_02,2025-03-28_01 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 mlxlogscore=999 mlxscore=0 suspectscore=0 bulkscore=0 clxscore=1030 adultscore=0 phishscore=0 spamscore=0 malwarescore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.22.0-2503310001 definitions=main-2505260168 From: alwin@garsi.de (Alwin Garside) Hey all, It took me a while, but I'm finally caught up with this thread, and = would like to give my 2 cents. On 25 May 2025, at 23:17, Rowan Tommins [IMSoP] = wrote: >=20 > On 25/05/2025 21:28, Larry Garfield wrote: >> Even if we develop some way such that in Foo.php, loading the class = \Beep\Boop\Narf pulls from /beep/boop/v1/Narf.php and loading it from = Bar.php pulls the same class from /beep/boop/v2/Narf.php, and does = something or other to keep the symbols separate... Narf itself is going = to load \Beep\Boop\Poink at some point. So which one does it get? Or = rather, there's now two Narfs. How do they know that the v1 version of = Narf should get the v1 version of Poink and the v2 version should get = the v2 version. >=20 >=20 > The prefixing, in my mind, has nothing to do with versions. There is = no "v1" and "v2" directory, there are just two completely separate = "vendor" directories, with the same layout we have right now. >=20 > So it goes like this: >=20 > 1. Some code in wp-plugins/AlicesCalendar/vendor/Beep/Boop/Narf.php = mentions a class called \Beep\Boop\Poink > 2. The Container mechanism has rewritten this to = \__Container\AlicesCalendar\Beep\Boop\Poink, but that isn't defined yet > 3. The isolated autoloader stack (loaded from = wp-plugins/AlicesCalendar/vendor/autoload.php) is asked for the original = name, \Beep\Boop\Poink > 4. It includes the file = wp-plugins/AlicesCalendar/vendor/Beep/Boop/Poink.php which contains the = defintion of \Beep\Boop\Poink > 5. The Container mechanism rewrites the class to = \__Container\AlicesCalendar\Beep\Boop\Poink and carries on >=20 > When code in wp-plugins/BobsDocs/vendor/Beep/Boop/Narf.php mentions = \Beep\Boop\Poink, the same thing happens, but with a completely separate = sandbox: the rewritten class name is = \__Container\BobsDocs\Beep\Boop\Poink, and the autoloader was loaded = from wp-plugins/BobsDocs/vendor/autoload.php In this thread I see a lot of talking about Composer and autoloaders. = But in essence those are just tools we use to include files into our = current PHP process, so for the sake of simplicity (and compatibility), = let's disregard all of that for a moment. Instead, please bear with me = while we do a little gedankenexperiment... First, imagine one gigantic PHP file, `huge.php`, that contains all the = PHP code that is included from all the libraries you need during a = single PHP process lifecycle. That is in the crudest essence how PHP's = include system currently works: files get included, those files declare = symbols within the scope of the current process. If you were to = copy-paste all the code you need (disregarding the `declare()` = statements) in one huge PHP file, you essentially get the same result. So in our thought experiment we'll be doing just that. The only rule is = that we copy all the code verbatim (again, disregarding the `declare()` = statements), because that's how PHP includes also work. ```php foo =3D new $fooClass(); } } namespace Spam; use Acme\Bar; class Ham extends Bar {} namespace Spam; use Acme\Bar; class Bacon extends Bar {} // ... ``` Now, the problem here is that if we copy-paste two different versions of = the same class with the same FQN into our `huge.php` file they will try = to declare the same symbols which will cause a conflict. Let's say our = `Ham` depends on one version of `Acme\Bar` and our `Bacon` depends on = another version of `Acme\Bar`: ```php foo =3D new $fooClass(); } } namespace Spam; use Acme\Bar; class Ham extends Bar {} namespace Acme; class Foo {} // Fatal error: Cannot declare class Foo, because the name = is already in use namespace Acme; class Bar { // Fatal error: Cannot declare class Bar, because the name = is already in use public readonly Foo $foo; public function __construct() { // For some reason, there is a string reference here. Don't ask. $fooClass =3D '\Acme\Foo'; $this->foo =3D new $fooClass(); } } namespace Spam; use Acme\Bar; class Bacon extends Bar {} // ... ``` So how do we solve this in a way that we can copy-paste the code from = both versions of `Acme\Foo`, verbatim into `huge.php`? Well, one way is to break the single rule we have created: modify the = code. What if we just let the engine quietly rewrite the code? Well, = then we quickly run into an issue. Any non-symbol references to classes = are hard to detect and rewrite, so this would break: ```php foo =3D new $fooClass(); } } namespace Spam; use Acme\Bar; class Ham extends Bar {} namespace Spam\Bacon\Acme; // Quietly rewritten class Foo {} namespace Spam\Bacon\Acme; // Quietly rewritten class Bar { public readonly Foo $foo; public function __construct() { // For some reason, there is a string reference here. Don't ask. $fooClass =3D '\Acme\Foo'; // <=3D=3D Whoops, missed this = one!!! $this->foo =3D new $fooClass(); } } namespace Spam; use Spam\Bacon\Acme\Bar; // Quietly rewritten class Bacon extends Bar {} // ... ``` So let's just follow our rule for now. Now how do we include Foo and Bar = twice? Well, let's try Rowan's approach of "containerizing." Let's take = a very naive approach to what that syntax might look like. We simply = copy-paste the code for our second version of `Acme\Bar` into the scope = of a container. For the moment let's assume that the container works = like a "UnionFS" of sorts, where symbols declared inside the container = override any symbols that may already exist outside the container:=20 ```php foo =3D new $fooClass(); } } namespace Spam; use Acme\Bar; class Ham extends Bar {} container Bacon_Acme { namespace Acme; class Foo {} =20 namespace Acme; class Bar { public readonly Foo $foo; public function __construct() { // For some reason, there is a string reference here. Don't = ask. $fooClass =3D '\Acme\Foo'; $this->foo =3D new $fooClass(); } } } namespace Spam; use Bacon_Acme\\Acme\Bar; class Bacon extends Bar {} // ... ``` That seems like it could work. As you can see, I've decided to use = double backspace (`\\`) to separate container and namespace in this = example. You may wonder how this would look in the real world, where not = all code is copy-pasted into a single `huge.php`. Of course, part of = this depends on the autoloader implementation, but let's start with a = first step of abstraction by replacing the copy-pasted code with = includes: ```php // ... require_once '../vendor/acme/acme/Foo.php'; require_once '../vendor/acme/acme/Bar.php'; namespace Spam; use Acme\Bar; class Ham extends Bar {} container Bacon_Acme { require_once '../lib/acme/acme/Foo.php'; require_once '../lib/acme/acme/Bar.php'; } namespace Spam; use Bacon_Acme\\Acme\Bar; class Bacon extends Bar {} ``` Now what if we want the autoloader to be able to resolve this? Well, = once a class symbol is resolved with a container prefix, it would have = to also perform all its includes inside the scope of that container.=20 ```php function autoload($class_name) { // Do autoloading shizzle. } spl_autoload_register(); namespace Spam; use Bacon_Acme\\Acme\Bar; class Bacon extends Bar {} // Meanwhile, behind the scenes, in the autoloader:=20 container Bacon_acme { autoload(Acme\Bar::class); } ``` Now this mail is already quite long enough, so I'm gonna wrap it up here = and do some more brainstorming. But I hope I may have inspired some of = you. All in all, I think my approach might actually work, although I = haven't even considered what the implementation would even look like. Again, the main point I want to make is to just disregard composer and = the autoloader for now; those are just really fancy wrappers around = import statements. Whatever solution we end up with would have to work = independently of Composer and/or the autoloader. Alwin