diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index a36398a47ca7..38b35bdafb2f 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -176,6 +176,13 @@ class Container implements ArrayAccess, ContainerContract */ protected $afterResolvingAttributeCallbacks = []; + /** + * An array of the interfaces to AutoConfigure + * + * @var class-string[] + */ + protected $autoconfigure = []; + /** * Define a contextual binding. * @@ -1693,4 +1700,12 @@ public function __set($key, $value) { $this[$key] = $value; } + + /** + * @param class-string $interface + */ + public function autoconfigure(string $interface): void + { + $this->autoconfigure[] = $interface; + } } diff --git a/src/Illuminate/Container/Util.php b/src/Illuminate/Container/Util.php index ebd345efac27..af4acbc99d52 100644 --- a/src/Illuminate/Container/Util.php +++ b/src/Illuminate/Container/Util.php @@ -12,6 +12,11 @@ */ class Util { + /** + * @var array> + */ + private static array $localCache; + /** * If the given value is not an array and not null, wrap it in one. * @@ -84,4 +89,58 @@ public static function getContextualAttributeFromDependency($dependency) { return $dependency->getAttributes(ContextualAttribute::class, ReflectionAttribute::IS_INSTANCEOF)[0] ?? null; } + + /** + * This has been extracted from API Platform + * + * @param string $directory + * + * @return array + */ + public static function getReflectionClassesFromDirectory(string $directory): array + { + $id = hash('xxh3', $directory); + if (isset(self::$localCache[$id])) { + return self::$localCache[$id]; + } + + $includedFiles = []; + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+\.php$/i', + \RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = $file[0]; + + if (!preg_match('(^phar:)i', (string) $sourceFile)) { + $sourceFile = realpath($sourceFile); + } + + try { + require_once $sourceFile; + } catch (\Throwable) { + // invalid PHP file (example: missing parent class) + continue; + } + + $includedFiles[$sourceFile] = true; + } + + $declared = array_merge(get_declared_classes(), get_declared_interfaces()); + $ret = []; + foreach ($declared as $className) { + $reflectionClass = new \ReflectionClass($className); + $sourceFile = $reflectionClass->getFileName(); + if (isset($includedFiles[$sourceFile])) { + $ret[$className] = $reflectionClass; + } + } + + return self::$localCache[$id] = $ret; + } } diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 38378aa3605d..b649c019a1a0 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -5,6 +5,7 @@ use Closure; use Composer\Autoload\ClassLoader; use Illuminate\Container\Container; +use Illuminate\Container\Util; use Illuminate\Contracts\Console\Kernel as ConsoleKernelContract; use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Contracts\Foundation\CachesConfiguration; @@ -224,6 +225,7 @@ public function __construct($basePath = null) $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); $this->registerLaravelCloudServices(); + $this->registerAutoConfigurableServices(); } /** @@ -1716,4 +1718,19 @@ public function getNamespace() throw new RuntimeException('Unable to detect application namespace.'); } + + protected function registerAutoConfigurableServices(): void + { + $classes = Util::getReflectionClassesFromDirectory(app_path()); + foreach ($this->autoconfigure as $interface) { + $services = []; + foreach ($classes as $className => $refl) { + if ($refl->implementsInterface($interface)) { + $services[] = $className; + } + } + + $this->app->tag($services, $interface); + } + } }