diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 0ee764ff..0708f658 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -428,7 +428,7 @@ public function hasBeenBootstrapped() * ponytail: v13 bootstraps through its kernels (environment, configuration, exception * handling, facades, providers). The fork runs start.php in their place, the first time * handleRequest(), handleCommand() or the console Kernel's bootstrap() asks for it. - * The environment defaults to production, as in v13. Remove at the flip. + * Remove at the flip. * * @return void */ @@ -438,12 +438,8 @@ public function bootstrapWithStartScript() $this->hasBeenBootstrapped = true; - if ( ! $this->bound('env')) $this->detectEnvironment(array()); - $app = $this; - $env = $this['env']; - require static::getBootstrapFile(); } @@ -488,14 +484,16 @@ public function isLocal() /** * Detect the application's current environment. * - * @param array|string $envs + * @param \Closure $callback * @return string */ - public function detectEnvironment($envs) + public function detectEnvironment(Closure $callback) { - $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null; + $args = $this->runningInConsole() && isset($_SERVER['argv']) + ? $_SERVER['argv'] + : null; - return $this['env'] = (new EnvironmentDetector())->detect($envs, $args); + return $this['env'] = (new EnvironmentDetector)->detect($callback, $args); } /** diff --git a/src/Illuminate/Foundation/EnvironmentDetector.php b/src/Illuminate/Foundation/EnvironmentDetector.php index 859e1947..6632c666 100644 --- a/src/Illuminate/Foundation/EnvironmentDetector.php +++ b/src/Illuminate/Foundation/EnvironmentDetector.php @@ -1,6 +1,5 @@ detectConsoleEnvironment($environments, $consoleArgs); + return $this->detectConsoleEnvironment($callback, $consoleArgs); } - return $this->detectWebEnvironment($environments); + return $this->detectWebEnvironment($callback); } /** * Set the application environment for a web request. * - * @param array|string $environments + * @param \Closure $callback * @return string */ - protected function detectWebEnvironment($environments): string - { - // If the given environment is just a Closure, we will defer the environment check - // to the Closure the developer has provided, which allows them to totally swap - // the webs environment detection logic with their own custom Closure's code. - if ($environments instanceof Closure) - { - return call_user_func($environments); - } - - foreach ($environments as $environment => $hosts) - { - // To determine the current environment, we'll simply iterate through the possible - // environments and look for the host that matches the host for this request we - // are currently processing here, then return back these environment's names. - foreach ((array) $hosts as $host) - { - if ($this->isMachine($host)) return $environment; - } - } - - return 'production'; + protected function detectWebEnvironment(Closure $callback) + { + return $callback(); } /** * Set the application environment from command-line arguments. * - * @param mixed $environments + * @param \Closure $callback * @param array $args * @return string */ - protected function detectConsoleEnvironment($environments, array $args): string - { - // First we will check if an environment argument was passed via console arguments - // and if it was that automatically overrides as the environment. Otherwise, we - // will check the environment as a "web" request like a typical HTTP request. + protected function detectConsoleEnvironment(Closure $callback, array $args) + { if ( ! is_null($value = $this->getEnvironmentArgument($args))) { - return head(array_slice(explode('=', $value), 1)); + return $value; } - return $this->detectWebEnvironment($environments); + return $this->detectWebEnvironment($callback); } /** @@ -78,23 +55,20 @@ protected function detectConsoleEnvironment($environments, array $args): string * @param array $args * @return string|null */ - protected function getEnvironmentArgument(array $args): ?string - { - return Arr::first($args, function($value, $key) + protected function getEnvironmentArgument(array $args) + { + foreach ($args as $i => $value) { - return starts_with($value, '--env'); - }); - } + if ($value === '--env') + { + return $args[$i + 1] ?? null; + } - /** - * Determine if the name matches the machine name. - * - * @param string $name - * @return bool - */ - public function isMachine($name): bool - { - return str_is($name, gethostname()); + if (str_starts_with($value, '--env=')) + { + return head(array_slice(explode('=', $value), 1)); + } + } } } diff --git a/src/Illuminate/Foundation/start.php b/src/Illuminate/Foundation/start.php index 395455c2..f2094ccf 100755 --- a/src/Illuminate/Foundation/start.php +++ b/src/Illuminate/Foundation/start.php @@ -44,6 +44,7 @@ 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; @@ -95,14 +96,19 @@ | Register The Environment Variables |-------------------------------------------------------------------------- | -| v13's LoadEnvironmentVariables loads the .env file first. The L4.2 -| .env.{env}.php file loads after it, so its values win on the keys both -| files define. +| v13's LoadEnvironmentVariables loads the .env file first. Unless the +| application has set its environment, APP_ENV then names it, defaulting to +| production as v13's config does. The L4.2 .env.{env}.php file loads after +| that, so its values win on the keys both files define. | */ $app->make(LoadEnvironmentVariables::class)->bootstrap($app); +if ( ! $app->bound('env')) $app->detectEnvironment(fn() => Env::get('APP_ENV', 'production')); + +$env = $app['env']; + with($envVariables = new EnvironmentVariables( $app->getEnvironmentVariablesLoader()))->load($env); diff --git a/tests/Foundation/FoundationApplicationBuilderTest.php b/tests/Foundation/FoundationApplicationBuilderTest.php index d7587793..bdaf1027 100644 --- a/tests/Foundation/FoundationApplicationBuilderTest.php +++ b/tests/Foundation/FoundationApplicationBuilderTest.php @@ -68,7 +68,7 @@ protected function tearDown(): void error_reporting($this->errorReporting); ini_set('display_errors', $this->displayErrors); date_default_timezone_set($this->timezone); - foreach (array('FORK_START_SHARED', 'FORK_START_DOTENV') as $key) + foreach (array('FORK_START_SHARED', 'FORK_START_DOTENV', 'APP_ENV') as $key) { putenv($key); unset($_ENV[$key], $_SERVER[$key]); @@ -227,6 +227,35 @@ public function theEnvironmentDefaultsToProduction() $this->assertSame('production', $app['env']); } + #[Test] + public function appEnvFromTheEnvFileNamesTheEnvironmentAndItsPhpFile() + { + file_put_contents($this->base.'/.env', "APP_ENV=staging\n"); + file_put_contents($this->base.'/.env.staging.php', ' "staging");'); + $app = Application::configure($this->base)->create(); + + $app->bootstrapWithStartScript(); + $this->bootstraps++; + + $this->assertSame('staging', $app['env']); + $this->assertSame('staging', getenv('FORK_START_SHARED')); + } + + #[Test] + public function anEnvironmentTheApplicationDetectedWinsOverAppEnv() + { + file_put_contents($this->base.'/.env', "APP_ENV=staging\n"); + file_put_contents($this->base.'/.env.local.php', ' "local");'); + $app = Application::configure($this->base)->create(); + $app->detectEnvironment(fn () => 'local'); + + $app->bootstrapWithStartScript(); + $this->bootstraps++; + + $this->assertSame('local', $app['env']); + $this->assertSame('local', getenv('FORK_START_SHARED')); + } + #[Test] public function handleRequestBootstrapsSendsTheResponseAndTerminates() { diff --git a/tests/Foundation/FoundationApplicationTest.php b/tests/Foundation/FoundationApplicationTest.php index 3949e8df..3d374059 100755 --- a/tests/Foundation/FoundationApplicationTest.php +++ b/tests/Foundation/FoundationApplicationTest.php @@ -170,6 +170,34 @@ public function testEnvironment() $this->assertFalse($app->environment('qux', 'bar')); $this->assertFalse($app->environment(['qux', 'bar'])); } + + + public function testDetectEnvironmentTakesTheConsolesEnvOptionOverTheCallback() + { + $argv = $_SERVER['argv']; + $_SERVER['argv'] = array('artisan', 'migrate', '--env', 'staging'); + + try + { + $app = new Application; + + $this->assertSame('staging', $app->detectEnvironment(fn () => 'local')); + $this->assertSame('staging', $app['env']); + } + finally + { + $_SERVER['argv'] = $argv; + } + } + + + public function testDetectEnvironmentAsksTheCallbackWithoutAnEnvOption() + { + $app = new Application; + + $this->assertSame('local', $app->detectEnvironment(fn () => 'local')); + $this->assertSame('local', $app['env']); + } } class ApplicationCustomExceptionHandlerStub extends Illuminate\Foundation\Application { diff --git a/tests/Foundation/FoundationEnvironmentDetectorTest.php b/tests/Foundation/FoundationEnvironmentDetectorTest.php index ad707dcf..3a334f32 100644 --- a/tests/Foundation/FoundationEnvironmentDetectorTest.php +++ b/tests/Foundation/FoundationEnvironmentDetectorTest.php @@ -1,56 +1,36 @@ makePartial(); - $env->shouldReceive('isMachine')->once()->with('localhost')->andReturn(false); - $result = $env->detect( - [ - 'local' => ['localhost'] - ] - ); - $this->assertEquals('production', $result); - - - $env = m::mock(EnvironmentDetector::class)->makePartial(); - $env->shouldReceive('isMachine')->once()->with('localhost')->andReturn(true); - $result = $env->detect([ - 'local' => ['localhost'] - ]); - $this->assertEquals('local', $result); + #[Test] + public function theCallbackNamesTheEnvironment() + { + $this->assertSame('foobar', (new EnvironmentDetector)->detect(fn () => 'foobar')); } - - public function testClosureCanBeUsedForCustomEnvironmentDetection(): void - { - $env = new Illuminate\Foundation\EnvironmentDetector; - - $result = $env->detect(function() { return 'foobar'; }); - $this->assertEquals('foobar', $result); + #[Test] + public function anEnvOptionWithAnEqualsSignOverridesTheCallback() + { + $this->assertSame('local', (new EnvironmentDetector)->detect(fn () => 'foobar', array('artisan', '--env=local'))); } + #[Test] + public function anEnvOptionFollowedByItsValueOverridesTheCallback() + { + $this->assertSame('local', (new EnvironmentDetector)->detect(fn () => 'foobar', array('artisan', 'migrate', '--env', 'local'))); + } - public function testConsoleEnvironmentDetection(): void - { - $env = new Illuminate\Foundation\EnvironmentDetector; + #[Test] + public function consoleArgumentsWithoutAnEnvOptionLeaveItToTheCallback() + { + $detector = new EnvironmentDetector; - $result = $env->detect([ - 'local' => ['foobar'] - ], ['--env=local']); - $this->assertEquals('local', $result); + $this->assertSame('foobar', $detector->detect(fn () => 'foobar', array('artisan', 'migrate', '--force'))); + $this->assertSame('foobar', $detector->detect(fn () => 'foobar', array('artisan', '--env'))); + $this->assertSame('foobar', $detector->detect(fn () => 'foobar', array('artisan', '--environment=local'))); } - }