在laravel框架中对环境配置文件的加载过程步骤浅析
在laravel中是支持配置加载不同环境配置文件的,下面来看看laravel框架在不同的环境自动加载不同的环境配置过程。
一.环境配置的加载过程:
运行 laravel 框架主要有两种方式,
一种是作为 web服务的运行的 laravel,
另一种是作为命令行脚本运行的 laravel。
web 服务的入口文件是 public/index.php , 命令行脚本的入口文件是 artisan。
可以说框架的加载也是从 public/index.php 或 artisan 开始的。
我们拿 public/index.php 举例, 可以看到先引用了 laravel 框架,
然后通过服务提供者 make 出来一个 Kernel ,
然后通过调用 Kernel 的 handle 方法 和 send 方法创建了 $response 和 $request 实例。
# public/index.php 和 artisan 中的这一行代码引入了 laravel 框架。
$app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
在 /bootstrap/app.php 中通过下面这行代码创建的 laravel 实例。
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
在 Illuminate\Foundation\Application 中有一个 bootstrapWith 方法,
这个方法先通过 $this->make() 方法创建了 bootstrapper 的实例,
然后执行 bootstrapper 的 bootstrap 方法。
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
内核 Kernel 中 bootstrap 方法调用了 Application 中的 bootstrapWith 方法,
这里还是拿 Illuminate\Foundation\Http\Kernel 举例子,
可以看到 $bootstrappers 中的第一个 bootstrapper 就是
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables,
这个 bootstrapper 就是用来加载环境配置的 bootstrapper。
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
...
public function bootstrap(){
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
我们接着来看下
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables 的 bootstrap 方法,
$app->configurationIsCached() 为 true 表示有配置的缓存文件,如果有的话就不会重复加载。
然后 $this->checkForSpecificEnvironmentFile($app); 检查特定环境配置文件。
这个方法检查会修改 $app 中环境配置文件的路径, 所以是今天的重点。
public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
$this->createDotenv($app)->safeLoad();
} catch (InvalidFileException $e) {
$this->writeErrorAndDie($e);
}
}
首先判断是不是命令行环境,如果是的话, 会获取 --env 的值,
并将这个值和 $app->environmentFile() 和 . 做字符串拼接, 然后调用 setEnvironmentFilePath 修改环境配置文件的文件名, 比如说 --env=local 那最后加载的环境配置文件就是 .env.local。
这里如果不是命令行环境的话, 后面会通过 Env::get('APP_ENV');
获取当前环境, 然后同样做一个字符串拼接, 然后调用 setEnvironmentFilePath 修改。
protected function checkForSpecificEnvironmentFile($app)
{
if ($app->runningInConsole() &&
($input = new ArgvInput)->hasParameterOption('--env') &&
$this->setEnvironmentFilePath($app, $app->environmentFile().'.'.$input->getParameterOption('--env'))) {
return;
}
$environment = Env::get('APP_ENV');
if (! $environment) {
return;
}
$this->setEnvironmentFilePath(
$app, $app->environmentFile().'.'.$environment
);
}
protected function setEnvironmentFilePath($app, $file)
{
if (is_file($app->environmentPath().'/'.$file)) {
$app->loadEnvironmentFrom($file);
return true;
}
return false;
}
如果在 checkForSpecificEnvironmentFile 方法中没有修改环境配置文件的文件名, 则会加载默认的环境配置文件 。
这个默认的文件名是通过 \Illuminate\Foundation\Application 的 $environmentFile 属性定义的,
前面的 setEnvironmentFilePath 修改的也是 environmentFile 的值。
protected $environmentFile = '.env';
public function loadEnvironmentFrom($file)
{
$this->environmentFile = $file;
return $this;
}
这里再说明一下, Illuminate\Foundation\Http\Kernel的bootstrap方法是在sendRequestThroughRouter方法中调用的, 而 sendRequestThroughRouter 是在 handle 方法中调用的。
public function handle($request)
{
$this->requestStartedAt = Carbon::now();
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
我们来整理一下。
整个环境配置加载过程就是 先有了 $app,
然后 $kernel = $app->make(Kernel::class)
再然后 $kernel->handle() 在 handle 方法中调用了 $kernel->bootstrap()
最后在 bootstrap 中通过 LoadEnvironmentVariables 加载了环境配置.
二.如何使 laravel 在不同的环境自动加载不同的环境配置。
现在 env 的加载过程基本上已经了解了,
我们接着再详细说明下 checkForSpecificEnvironmentFile 中 Env::get('APP_ENV') 的具体逻辑,
以及如何使 laravel 在不同的环境下自动加载不同环境配置的具体操作。
get 方法没什么好说就是 static::getRepository() 然后尝试获取配置项的值,
关键是它有内些仓库。
public static function get($key, $default = null)
{
return Option::fromValue(static::getRepository()->get($key))
->map(function ($value) {
switch (strtolower($value)) {
case 'true':
case '(true)':
return true;
case 'false':
case '(false)':
return false;
case 'empty':
case '(empty)':
return '';
case 'null':
case '(null)':
return;
}
if (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) {
return $matches[2];
}
return $value;
})
->getOrCall(fn () => value($default));
}
其实就是 EnvConstAdapter ServerConstAdapter, PutenvAdapter 它们三个。
public static function getRepository()
{
if (static::$repository === null) {
$builder = RepositoryBuilder::createWithDefaultAdapters();
if (static::$putenv) {
$builder = $builder->addAdapter(PutenvAdapter::class);
}
static::$repository = $builder->immutable()->make();
}
return static::$repository;
}
final class RepositoryBuilder
{
/**
* The set of default adapters.
*/
private const DEFAULT_ADAPTERS = [
ServerConstAdapter::class,
EnvConstAdapter::class,
];
Dotenv\Repository\Adapter\ServerConstAdapter 的 read 方法其实就是从 $_SERVER 中读。
public function read(string $name)
{
/** @var \PhpOption\Option<string> */
return Option::fromArraysValue($_SERVER, $name)
->map(static function ($value) {
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
return $value;
})->filter(static function ($value) {
return \is_string($value);
});
}
Dotenv\Repository\Adapter\EnvConstAdapter 的 read 方法是从 $_ENV 中读。
public function read(string $name)
{
/** @var \PhpOption\Option<string> */
return Option::fromArraysValue($_ENV, $name)
->map(static function ($value) {
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
return $value;
})->filter(static function ($value) {
return \is_string($value);
});
}
Dotenv\Repository\Adapter\PutenvAdapter 是从 \getenv() 中读,
\getenv 返回的其实就是 $_SERVER 和 $_ENV 中的值。
public function read(string $name)
{
/** @var \PhpOption\Option<string> */
return Option::fromValue(\getenv($name), false)->filter(static function ($value) {
return \is_string($value);
});
}
总结
使 Laravel 在不同的环境自动加载不同的环境配置其实很简单, 在命令行环境时,
我们只要在后面加选项 --env=* 就可以使 laravel 加载不同的环境配置文件。
比如
php artisan horizon --env=local
在 web 环境下我们只要修改 $_ENV 或者 $_SERVER 中 APP_ENV 的值就可以使 laravel 加载不同的环境配置文件。
相关文章