diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0708f658..03bce1c4 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 00000000..5eeccf0a --- /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 00000000..68314762 --- /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 a450dfc2..75e2651d 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 59446153..efc8bcb8 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 f2094ccf..de0c8936 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/src/Illuminate/Support/helpers.php b/src/Illuminate/Support/helpers.php index abd3ed67..5a233f80 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 bdaf1027..b48432ab 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,76 @@ 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 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() + { + $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 +380,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 +438,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')