1313
1414namespace CodeIgniter \Autoloader ;
1515
16+ use Closure ;
1617use CodeIgniter \Exceptions \ConfigException ;
1718use CodeIgniter \Exceptions \InvalidArgumentException ;
1819use CodeIgniter \Exceptions \RuntimeException ;
@@ -93,6 +94,18 @@ 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+
105+ public function __construct (private readonly string $ composerPath = COMPOSER_PATH )
106+ {
107+ }
108+
96109 /**
97110 * Reads in the configuration array (described above) and stores
98111 * the valid parts that we'll need.
@@ -127,7 +140,7 @@ public function initialize(Autoload $config, Modules $modules)
127140 $ this ->helpers = [...$ this ->helpers , ...$ config ->helpers ];
128141 }
129142
130- if (is_file (COMPOSER_PATH )) {
143+ if (is_file ($ this -> composerPath )) {
131144 $ this ->loadComposerAutoloader ($ modules );
132145 }
133146
@@ -139,11 +152,11 @@ private function loadComposerAutoloader(Modules $modules): void
139152 // The path to the vendor directory.
140153 // We do not want to enforce this, so set the constant if Composer was used.
141154 if (! defined ('VENDORPATH ' )) {
142- define ('VENDORPATH ' , dirname (COMPOSER_PATH ) . DIRECTORY_SEPARATOR );
155+ define ('VENDORPATH ' , dirname ($ this -> composerPath ) . DIRECTORY_SEPARATOR );
143156 }
144157
145158 /** @var ClassLoader $composer */
146- $ composer = include COMPOSER_PATH ;
159+ $ composer = include $ this -> composerPath ;
147160
148161 // Should we load through Composer's namespaces, also?
149162 if ($ modules ->discoverInComposer ) {
@@ -166,8 +179,17 @@ private function loadComposerAutoloader(Modules $modules): void
166179 */
167180 public function register ()
168181 {
169- spl_autoload_register ($ this ->loadClassmap (...), true );
170- 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 );
171193
172194 foreach ($ this ->files as $ file ) {
173195 $ this ->includeFile ($ file );
@@ -179,8 +201,11 @@ public function register()
179201 */
180202 public function unregister (): void
181203 {
182- spl_autoload_unregister ($ this ->loadClass (...));
183- spl_autoload_unregister ($ this ->loadClassmap (...));
204+ foreach ($ this ->registeredClosures as $ closure ) {
205+ spl_autoload_unregister ($ closure );
206+ }
207+
208+ $ this ->registeredClosures = [];
184209 }
185210
186211 /**
@@ -451,14 +476,12 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa
451476 */
452477 protected function discoverComposerNamespaces ()
453478 {
454- if (! is_file (COMPOSER_PATH )) {
479+ if (! is_file ($ this -> composerPath )) {
455480 return ;
456481 }
457482
458- /**
459- * @var ClassLoader $composer
460- */
461- $ composer = include COMPOSER_PATH ;
483+ /** @var ClassLoader $composer */
484+ $ composer = include $ this ->composerPath ;
462485 $ paths = $ composer ->getPrefixesPsr4 ();
463486 $ classes = $ composer ->getClassMap ();
464487
0 commit comments