From 1f89b6a08e391dd1a11910df97ce4a2965118346 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 04:13:15 +0000 Subject: [PATCH 1/2] feat(foundation): register facades and providers with v13's bootstrappers; no app/start files (task 4.5) The start script now runs v13's RegisterFacades and RegisterProviders where it set up the facades, the alias loader and the providers. Facades point at the application after the configuration loads, as in v13. RegisterProviders merges the providers given to withProviders() and those in bootstrap/providers.php after app.providers, skipping classes that don't exist. Application::configure() calls withProviders(), so the bootstrap file is read by default. registerConfiguredProviders() registers the Illuminate providers first, each group in its configured order. The package manifest and the config cache come with the flip. The start script no longer loads app/start/global.php or app/start/{env}.php; v13 has no start files. The routes still load from app/routes.php once the application has booted. ProviderRepository's fresh manifest now carries the 'when' key, which it was missing when no provider is deferred. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0169SsatCE8LhaTsQTPiGtVo --- src/Illuminate/Foundation/Application.php | 43 ++++++++- .../Foundation/Bootstrap/RegisterFacades.php | 28 ++++++ .../Bootstrap/RegisterProviders.php | 95 +++++++++++++++++++ .../Configuration/ApplicationBuilder.php | 21 ++++ .../Foundation/ProviderRepository.php | 2 +- src/Illuminate/Foundation/start.php | 95 +++---------------- .../FoundationApplicationBuilderTest.php | 93 ++++++++++++++++++ 7 files changed, 294 insertions(+), 83 deletions(-) create mode 100644 src/Illuminate/Foundation/Bootstrap/RegisterFacades.php create mode 100644 src/Illuminate/Foundation/Bootstrap/RegisterProviders.php diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0708f658e..03bce1c4f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -239,7 +239,7 @@ protected function registerEventProvider() */ public static function configure(string $basePath) { - return new ApplicationBuilder((new static)->setBasePath($basePath)); + return (new ApplicationBuilder((new static)->setBasePath($basePath)))->withProviders(); } /** @@ -258,6 +258,7 @@ public function setBasePath($basePath) $this->instance('path.base', $basePath); $this->instance('path', $basePath.'/app'); + $this->instance('path.bootstrap', $basePath.'/bootstrap'); $this->instance('path.config', $basePath.'/config'); $this->instance('path.public', $basePath.'/public'); $this->instance('path.storage', $basePath.'/storage'); @@ -1170,6 +1171,46 @@ public function getEnvironmentVariablesLoader() return new FileEnvironmentVariablesLoader(new Filesystem, $this['path.base']); } + /** + * Get the path to the bootstrap directory. + * + * @param string $path + * @return string + */ + public function bootstrapPath($path = '') + { + return $this['path.bootstrap'].($path != '' ? DIRECTORY_SEPARATOR.$path : ''); + } + + /** + * Get the path to the service provider list in the bootstrap directory. + * + * @return string + */ + public function getBootstrapProvidersPath() + { + return $this->bootstrapPath('providers.php'); + } + + /** + * Register all of the configured providers. + * + * As in v13, the framework's own providers register before the others, each group in + * its configured order. + * + * @return void + */ + public function registerConfiguredProviders() + { + $providers = $this['config']->get('app.providers', array()); + + $framework = array_filter($providers, fn ($provider) => str_starts_with($provider, 'Illuminate\\')); + + $this->getProviderRepository()->load($this, array_merge( + array_values($framework), array_values(array_diff_key($providers, $framework)) + )); + } + /** * Get the service provider repository instance. * diff --git a/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php b/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php new file mode 100644 index 000000000..5eeccf0a4 --- /dev/null +++ b/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php @@ -0,0 +1,28 @@ +make('config')->get('app.aliases', array()))->register(); + } + +} diff --git a/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php new file mode 100644 index 000000000..683147624 --- /dev/null +++ b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php @@ -0,0 +1,95 @@ +mergeAdditionalProviders($app); + + $app->registerConfiguredProviders(); + } + + /** + * Merge the additional configured providers into the configuration. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function mergeAdditionalProviders(Application $app) + { + if (static::$bootstrapProviderPath && file_exists(static::$bootstrapProviderPath)) + { + $packageProviders = require static::$bootstrapProviderPath; + + foreach ($packageProviders as $index => $provider) + { + if ( ! class_exists($provider)) + { + unset($packageProviders[$index]); + } + } + } + + $app->make('config')->set('app.providers', array_merge( + $app->make('config')->get('app.providers', array()), + static::$merge, + array_values($packageProviders ?? array()) + )); + } + + /** + * Merge the given providers into the provider configuration before registration. + * + * @param array $providers + * @param string|null $bootstrapProviderPath + * @return void + */ + public static function merge(array $providers, ?string $bootstrapProviderPath = null) + { + static::$bootstrapProviderPath = $bootstrapProviderPath; + + static::$merge = array_values(array_filter(array_unique( + array_merge(static::$merge, $providers) + ))); + } + + /** + * Flush the bootstrapper's global state. + * + * @return void + */ + public static function flushState() + { + static::$bootstrapProviderPath = null; + + static::$merge = array(); + } + +} diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index a450dfc28..75e2651d5 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -1,6 +1,7 @@ app->getBootstrapProvidersPath() : null + ); + + return $this; + } + /** * Register additional Artisan commands with the application. * diff --git a/src/Illuminate/Foundation/ProviderRepository.php b/src/Illuminate/Foundation/ProviderRepository.php index 59446153d..efc8bcb84 100755 --- a/src/Illuminate/Foundation/ProviderRepository.php +++ b/src/Illuminate/Foundation/ProviderRepository.php @@ -214,7 +214,7 @@ protected function freshManifest(array $providers) { list($eager, $deferred) = array(array(), array()); - return compact('providers', 'eager', 'deferred'); + return compact('providers', 'eager', 'deferred') + $this->default; } /** diff --git a/src/Illuminate/Foundation/start.php b/src/Illuminate/Foundation/start.php index f2094ccf0..de0c89366 100755 --- a/src/Illuminate/Foundation/start.php +++ b/src/Illuminate/Foundation/start.php @@ -43,12 +43,12 @@ */ use Illuminate\Http\Request; -use Illuminate\Support\Facades\Facade; use Illuminate\Support\Env; -use Illuminate\Foundation\AliasLoader; use Illuminate\Config\EnvironmentVariables; use Illuminate\Foundation\Bootstrap\LoadConfiguration; use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables; +use Illuminate\Foundation\Bootstrap\RegisterFacades; +use Illuminate\Foundation\Bootstrap\RegisterProviders; /* |-------------------------------------------------------------------------- @@ -63,21 +63,6 @@ $app->instance('app', $app); -/* -|-------------------------------------------------------------------------- -| Load The Illuminate Facades -|-------------------------------------------------------------------------- -| -| The facades provide a terser static interface over the various parts -| of the application, allowing their methods to be accessed through -| a mixtures of magic methods and facade derivatives. It's slick. -| -*/ - -Facade::clearResolvedInstances(); - -Facade::setFacadeApplication($app); - /* |-------------------------------------------------------------------------- | Register Facade Aliases To Full Classes @@ -142,22 +127,17 @@ if ($env != 'testing') ini_set('display_errors', 'Off'); -$config = $app['config']['app']; - /* |-------------------------------------------------------------------------- -| Register The Alias Loader +| Register The Facades |-------------------------------------------------------------------------- | -| The alias loader is responsible for lazy loading the class aliases setup -| for the application. We will only register it if the "config" service -| is bound in the application since it contains the alias definitions. +| v13's RegisterFacades points the facades at this application and +| registers the class aliases from the app.aliases configuration. | */ -$aliases = $config['aliases']; - -AliasLoader::getInstance($aliases)->register(); +$app->make(RegisterFacades::class)->bootstrap($app); /* |-------------------------------------------------------------------------- @@ -174,76 +154,29 @@ /* |-------------------------------------------------------------------------- -| Register The Core Service Providers +| Register The Service Providers |-------------------------------------------------------------------------- | -| The Illuminate core service providers register all of the core pieces -| of the Illuminate framework including session, caching, encryption -| and more. It's simply a convenient wrapper for the registration. +| v13's RegisterProviders registers the app.providers configuration, then +| the providers given to withProviders() and those in bootstrap/providers.php. | */ -$providers = $config['providers']; - -$app->getProviderRepository()->load($app, $providers); +$app->make(RegisterProviders::class)->bootstrap($app); /* |-------------------------------------------------------------------------- -| Register Booted Start Files +| Load The Application Routes |-------------------------------------------------------------------------- | -| Once the application has been booted there are several "start" files -| we will want to include. We'll register our "booted" handler here -| so the files are included after the application gets booted up. +| Once the application has booted, the routes load from app/routes.php. +| v13 has no app/start files, so the start script no longer loads them. | */ -$app->booted(function() use ($app, $env) +$app->booted(function() use ($app) { - - /* - |-------------------------------------------------------------------------- - | Load The Application Start Script - |-------------------------------------------------------------------------- - | - | The start scripts gives this application the opportunity to override - | any of the existing IoC bindings, as well as register its own new - | bindings for things like repositories, etc. We'll load it here. - | - */ - - $path = $app['path'].'/start/global.php'; - - if (file_exists($path)) require $path; - - /* - |-------------------------------------------------------------------------- - | Load The Environment Start Script - |-------------------------------------------------------------------------- - | - | The environment start script is only loaded if it exists for the app - | environment currently active, which allows some actions to happen - | in one environment while not in the other, keeping things clean. - | - */ - - $path = $app['path']."/start/{$env}.php"; - - if (file_exists($path)) require $path; - - /* - |-------------------------------------------------------------------------- - | Load The Application Routes - |-------------------------------------------------------------------------- - | - | The Application routes are kept separate from the application starting - | just to keep the file a little cleaner. We'll go ahead and load in - | all of the routes now and return the application to the callers. - | - */ - $routes = $app['path'].'/routes.php'; if (file_exists($routes)) require $routes; - }); diff --git a/tests/Foundation/FoundationApplicationBuilderTest.php b/tests/Foundation/FoundationApplicationBuilderTest.php index bdaf1027a..9df759bff 100644 --- a/tests/Foundation/FoundationApplicationBuilderTest.php +++ b/tests/Foundation/FoundationApplicationBuilderTest.php @@ -6,6 +6,7 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Artisan; use Illuminate\Foundation\Bootstrap\LoadConfiguration; +use Illuminate\Foundation\Bootstrap\RegisterProviders; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Http\Request; @@ -48,6 +49,8 @@ protected function setUp(): void file_put_contents($this->base.'/storage/meta/services.json', json_encode(array('providers' => array(), 'eager' => array(), 'deferred' => array()))); file_put_contents($this->base.'/app/routes.php', 'get("/probe", fn () => "probed in ".$app["env"]);'); file_put_contents($this->base.'/app/start/artisan.php', 'base.'/app/start/global.php', 'base.'/app/start/testing.php', 'bootstraps > 0; $this->bootstraps--) { restore_error_handler(); @@ -93,6 +98,8 @@ public function configureBindsTheV13PathsUnderTheBasePath() $this->assertSame($this->base.'/app/lang', $app['path.lang']); $this->assertSame($this->base.'/config', $app['path.config']); $this->assertSame($this->base.'/config/app.php', $app->configPath('app.php')); + $this->assertSame($this->base.'/bootstrap', $app->bootstrapPath()); + $this->assertSame($this->base.'/bootstrap/providers.php', $app->getBootstrapProvidersPath()); $this->assertSame($app, $app->useStoragePath('/elsewhere/storage')); $this->assertSame('/elsewhere/storage', $app['path.storage']); @@ -296,6 +303,65 @@ public function withCommandsBuildsEveryRegisteredCommandThroughTheContainer() $this->assertSame('bound', BuilderTestCommand::$greeting); } + #[Test] + public function theRoutesLoadOnceBootedWithoutTheStartFiles() + { + $app = $this->bootstrapped('testing'); + + $app->boot(); + + $this->assertSame(array('probe'), array_map(fn ($route) => $route->uri(), $app['router']->getRoutes()->getRoutes())); + } + + #[Test] + public function theFrameworksProvidersRegisterBeforeTheOthers() + { + $this->writeConfig('app', $this->appConfig(array(BuilderTestFirstProvider::class, Illuminate\Cookie\CookieServiceProvider::class))); + + $loaded = array_keys($this->bootstrapped('testing')->getLoadedProviders()); + + $this->assertSame(array(Illuminate\Cookie\CookieServiceProvider::class, BuilderTestFirstProvider::class), array_values(array_intersect($loaded, array(BuilderTestFirstProvider::class, Illuminate\Cookie\CookieServiceProvider::class)))); + } + + #[Test] + public function withProvidersAndBootstrapProvidersRegisterAfterTheConfiguredOnes() + { + $this->writeConfig('app', $this->appConfig(array(BuilderTestFirstProvider::class))); + mkdir($this->base.'/bootstrap'); + file_put_contents($this->base.'/bootstrap/providers.php', 'base)->withProviders(array(BuilderTestSecondProvider::class))->create(); + $app['env'] = 'testing'; + $this->bootstraps++; + $app->make(Kernel::class)->bootstrap(); + + $this->assertSame( + array(BuilderTestFirstProvider::class, BuilderTestSecondProvider::class, BuilderTestThirdProvider::class), + array_values(array_filter(array_keys($app->getLoadedProviders()), fn ($provider) => str_starts_with($provider, 'BuilderTest'))) + ); + $this->assertSame(array(BuilderTestFirstProvider::class, BuilderTestSecondProvider::class, BuilderTestThirdProvider::class), $app['config']['app.providers']); + } + + #[Test] + public function configureReadsTheBootstrapProvidersFileByDefault() + { + mkdir($this->base.'/bootstrap'); + file_put_contents($this->base.'/bootstrap/providers.php', 'assertArrayHasKey(BuilderTestThirdProvider::class, $this->bootstrapped('testing')->getLoadedProviders()); + } + + #[Test] + public function theConfiguredAliasesResolveThroughTheFacadesApplication() + { + $this->writeConfig('app', $this->appConfig(array(), array('BuilderTestGreetingAlias' => BuilderTestGreeting::class))); + + $app = $this->bootstrapped('testing'); + + $this->assertSame($app, Facade::getFacadeApplication()); + $this->assertInstanceOf(BuilderTestGreeting::class, new BuilderTestGreetingAlias); + } + #[Test] public function theLaravel42EntryPointsAreGone() { @@ -303,6 +369,18 @@ public function theLaravel42EntryPointsAreGone() $this->assertFalse(method_exists(Application::class, 'bindInstallPaths')); } + private function appConfig(array $providers, array $aliases = array()): array + { + return array( + 'debug' => false, + 'url' => 'http://builder.test', + 'timezone' => date_default_timezone_get(), + 'aliases' => $aliases, + 'providers' => $providers, + 'manifest' => $this->base.'/storage/meta', + ); + } + private function writeConfig(string $name, array $items): void { if ( ! is_dir($directory = dirname($this->base.'/config/'.$name))) mkdir($directory, 0777, true); @@ -349,6 +427,21 @@ public function register() } } +class BuilderTestFirstProvider extends ServiceProvider +{ + public function register() + { + } +} + +class BuilderTestSecondProvider extends BuilderTestFirstProvider +{ +} + +class BuilderTestThirdProvider extends BuilderTestFirstProvider +{ +} + class BuilderTestGreeting { public function __construct(public string $word = 'autowired') From 05b31a3c4d4de9ce7f6b9b2ecb911609ccf12641 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 04:17:39 +0000 Subject: [PATCH 2/2] fix(support): app() resolves through Container::getInstance(), as in v13 (task 4.5) The fork's app() helper returned the facades' application. Now that the facades are set after the configuration loads, as in v13, configuration files calling storage_path() or app() got null. v13's app() reads the container instance, which the application sets when it's constructed, so the helpers work before the facades are set. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0169SsatCE8LhaTsQTPiGtVo --- src/Illuminate/Support/helpers.php | 13 +++++++------ .../Foundation/FoundationApplicationBuilderTest.php | 11 +++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index abd3ed679..5a233f80b 100755 --- a/src/Illuminate/Support/helpers.php +++ b/src/Illuminate/Support/helpers.php @@ -22,19 +22,20 @@ function action($name, $parameters = array()) if ( ! function_exists('app')) { /** - * Get the root Facade application instance. + * Get the available container instance. * - * @param string $make + * @param string|null $abstract + * @param array $parameters * @return mixed */ - function app($make = null) + function app($abstract = null, array $parameters = array()) { - if ( ! is_null($make)) + if (is_null($abstract)) { - return app()->make($make); + return Illuminate\Container\Container::getInstance(); } - return Illuminate\Support\Facades\Facade::getFacadeApplication(); + return Illuminate\Container\Container::getInstance()->make($abstract, $parameters); } } diff --git a/tests/Foundation/FoundationApplicationBuilderTest.php b/tests/Foundation/FoundationApplicationBuilderTest.php index 9df759bff..b48432ab8 100644 --- a/tests/Foundation/FoundationApplicationBuilderTest.php +++ b/tests/Foundation/FoundationApplicationBuilderTest.php @@ -351,6 +351,17 @@ public function configureReadsTheBootstrapProvidersFileByDefault() $this->assertArrayHasKey(BuilderTestThirdProvider::class, $this->bootstrapped('testing')->getLoadedProviders()); } + #[Test] + public function configurationFilesCanUsePathHelpersBeforeTheFacadesAreSet() + { + file_put_contents($this->base.'/config/paths.php', ' storage_path(), "app" => app()->environment());'); + + $config = $this->bootstrapped('testing')['config']; + + $this->assertSame($this->base.'/storage', $config['paths.storage']); + $this->assertSame('testing', $config['paths.app']); + } + #[Test] public function theConfiguredAliasesResolveThroughTheFacadesApplication() {