Skip to content

Commit 2aaf6b6

Browse files
authored
fix: store SPL closures in register() so unregister() can remove them (#10097)
1 parent 35c4872 commit 2aaf6b6

File tree

3 files changed

+66
-4
lines changed

3 files changed

+66
-4
lines changed

system/Autoloader/Autoloader.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Autoloader;
1515

16+
use Closure;
1617
use CodeIgniter\Exceptions\ConfigException;
1718
use CodeIgniter\Exceptions\InvalidArgumentException;
1819
use CodeIgniter\Exceptions\RuntimeException;
@@ -93,6 +94,14 @@ class Autoloader
9394
*/
9495
protected $helpers = ['url'];
9596

97+
/**
98+
* Stores the closures registered with spl_autoload_register()
99+
* so that unregister() can remove the exact same instances.
100+
*
101+
* @var list<Closure(string): void>
102+
*/
103+
private array $registeredClosures = [];
104+
96105
public function __construct(private readonly string $composerPath = COMPOSER_PATH)
97106
{
98107
}
@@ -170,8 +179,17 @@ private function loadComposerAutoloader(Modules $modules): void
170179
*/
171180
public function register()
172181
{
173-
spl_autoload_register($this->loadClassmap(...), true);
174-
spl_autoload_register($this->loadClass(...), true);
182+
// Store the exact Closure instances so unregister() can remove them.
183+
// First-class callable syntax (e.g. $this->loadClass(...)) creates a
184+
// new Closure object on every call, so we must reuse the same instances.
185+
$loadClassmap = $this->loadClassmap(...);
186+
$loadClass = $this->loadClass(...);
187+
188+
$this->registeredClosures[] = $loadClassmap;
189+
$this->registeredClosures[] = $loadClass;
190+
191+
spl_autoload_register($loadClassmap, true);
192+
spl_autoload_register($loadClass, true);
175193

176194
foreach ($this->files as $file) {
177195
$this->includeFile($file);
@@ -183,8 +201,11 @@ public function register()
183201
*/
184202
public function unregister(): void
185203
{
186-
spl_autoload_unregister($this->loadClass(...));
187-
spl_autoload_unregister($this->loadClassmap(...));
204+
foreach ($this->registeredClosures as $closure) {
205+
spl_autoload_unregister($closure);
206+
}
207+
208+
$this->registeredClosures = [];
188209
}
189210

190211
/**

tests/system/Autoloader/AutoloaderTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,45 @@ public function testServiceAutoLoaderFromShareInstances(): void
123123
$this->assertSame($expected, $actual);
124124
}
125125

126+
public function testUnregisterRemovesClosuresFromSplStack(): void
127+
{
128+
$countBefore = count(spl_autoload_functions());
129+
130+
$config = new Autoload();
131+
$modules = new Modules();
132+
$modules->discoverInComposer = false;
133+
$config->psr4 = ['CodeIgniter' => SYSTEMPATH];
134+
135+
$loader = new Autoloader();
136+
$loader->initialize($config, $modules)->register();
137+
138+
$this->assertCount($countBefore + 2, spl_autoload_functions());
139+
140+
$loader->unregister();
141+
142+
$this->assertCount($countBefore, spl_autoload_functions());
143+
}
144+
145+
public function testUnregisterRemovesAllClosuresAfterMultipleRegistrations(): void
146+
{
147+
$countBefore = count(spl_autoload_functions());
148+
149+
$config = new Autoload();
150+
$modules = new Modules();
151+
$modules->discoverInComposer = false;
152+
$config->psr4 = ['CodeIgniter' => SYSTEMPATH];
153+
154+
$loader = new Autoloader();
155+
$loader->initialize($config, $modules)->register();
156+
$loader->register();
157+
158+
$this->assertCount($countBefore + 4, spl_autoload_functions());
159+
160+
$loader->unregister();
161+
162+
$this->assertCount($countBefore, spl_autoload_functions());
163+
}
164+
126165
public function testServiceAutoLoader(): void
127166
{
128167
$autoloader = service('autoloader', false);

user_guide_src/source/changelogs/v4.7.3.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Deprecations
3030
Bugs Fixed
3131
**********
3232

33+
- **Autoloader:** Fixed a bug where ``Autoloader::unregister()`` (used during tests) silently failed to remove handlers from the SPL autoload stack, causing closures to accumulate permanently.
34+
3335
See the repo's
3436
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
3537
for a complete list of bugs fixed.

0 commit comments

Comments
 (0)