使Laravel 在不同的环境自动加载不同的环境配置

Laravel框架
614
0
0
2023-08-28
标签   Laravel环境

这篇文章会先说明一下环境配置的加载过程,然后再说明如何使 laravel 在不同的环境自动加载不同的环境配置。

一. 环境配置的加载过程:

运行 laravel 框架主要有两种方式, 一种是作为web服务的运行的laravel,另一种是作为命令行脚本运行的 laravel。web 服务的入口文件是 public/index.php , 命令行脚本的入口文件是 artisan。可以说框架的加载也是从 public/index.phpartisan 开始的。我们拿 public/index.php 举例, 可以看到先引用了 laravel 框架, 然后通过服务提供者 make 出来一个 Kernel , 然后通过调用 Kernelhandle 方法 和 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 的实例, 然后执行 bootstrapperbootstrap 方法。

 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]);
        }
    }

内核 Kernelbootstrap 方法调用了 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\LoadEnvironmentVariablesbootstrap 方法,$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\Kernelbootstrap 方法是在 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 的加载过程基本上已经了解了, 我们接着再详细说明下 checkForSpecificEnvironmentFileEnv::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\ServerConstAdapterread 方法其实就是从 $_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\EnvConstAdapterread 方法是从 $_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 或者 $_SERVERAPP_ENV 的值就可以使 laravel 加载不同的环境配置文件。

使Laravel 在不同的环境自动加载不同的环境配置

使Laravel 在不同的环境自动加载不同的环境配置

本来是要写别的, 结果发现一台电脑只能保留一篇草稿,被迫补完作业。