Larabelアプリケーションの初期化の流れ
artisan が叩かれると、PROJECT_ROOT/bootstrap/app.php が呼び出されます。その最初で Illuminate\Foundation\Application が生成されます。
PROJECT_ROOT/bootstrap/app.php 抜粋
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
Application クラスを見てみましょう。実体は PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Foundation/Application.php です。このクラスのコンストラクタと関連する処理の一部は以下です。
Illuminate/Foundation/Application::__construct | 関連メソッド
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
/**
* Set the base path for the application.
*
* @param string $basePath
* @return $this
*/
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
コンストラクタではまず、パス情報の初期化を行っています。アプリケーションコンテナが生成された時に渡された引数は $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) でした。それを引数として setBasePath() メソッドを呼び出しています。setBasePath() メソッドは渡されたパス情報を $this->basePath に格納し、 bindPathsInContainer()メソッドを呼び出しています。このメソッドではパスの情報をバインドしているようです。今追っている config は path.config として PROJECT_ROOT/configが設定されています。
次にregisterBaseBindings() メソッドが呼び出されています。 registerBaseBindings() メソッドでは以下の処理が行われています。
Illuminate/Foundation/Application::registerBaseBindings() 一部
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
PackageManifestという名前で第二引数で生成した PackageManifest クラスをアプリケーションコンテナにバインドしているようです。PackageManifest クラスに渡している三つの引数を追ってみましょう。まず Filesystem です。実体は PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php です。ファイルを管理するクラスのようです。次に上で初期化された $basePathが渡されます。そして最後にキャッシュパスが渡されます。キャッシュパスについては後ほど追ってみます。PackageManifest クラスを見てみましょう。コンストラクタに以下のような定義がされています。
Illuminate/Foundation/PackageManifest
/**
* Create a new package manifest instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $basePath
* @param string $manifestPath
* @return void
*/
public function __construct(Filesystem $files, $basePath, $manifestPath)
{
$this->files = $files;
$this->basePath = $basePath;
$this->manifestPath = $manifestPath;
$this->vendorPath = $basePath.'/vendor';
}
引数で渡された情報を変数に格納し、追加で vendorPath もハードコーディングで定義しています。
Application クラスはひとまず置いておいて、一番最初に叩かれる artisan ファイルを見てみましょう。 次の処理に$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class) とあります。これは、$app = require_once __DIR__.'/bootstrap/app.php' の中で以下のようにアプリケーションコンテナにシングルトンとして生成しています。
PROJECT_ROOT/bootstrap/app.php 抜粋
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
App\Console\Kernel は composer/autoload_classmap.php で 'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php' と定義されています。
つまり実体は PROJECT_ROOT/app/Console/Kernel.php のようです。このクラスは Illuminate\Foundation\Console\Kernel を継承しています。Illuminate\Foundation\Console\Kernel に以下のような定義がされています。
Illuminate\Foundation\Console\Kernel
/**
* The bootstrap classes for the application.
*
* @var array
*/
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\SetRequestForConsole::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
/**
* Run the console application.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface|null $output
* @return int
*/
public function handle($input, $output = null)
{
try {
$this->bootstrap();
return $this->getArtisan()->run($input, $output);
} catch (Throwable $e) {
$this->reportException($e);
$this->renderException($output, $e);
return 1;
}
}
/**
* Bootstrap the application for artisan commands.
*
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
$this->app->loadDeferredProviders();
if (! $this->commandsLoaded) {
$this->commands();
$this->commandsLoaded = true;
}
}
$bootstrappers に初期設定用のクラスが配列としてセットされています。LoadConfiguration とそれっぽい記述がありますね。
そして handle() メソッドですが、これは artisan ファイルに make() メソッドの直後に以下のように書かれています。
PROJECT_ROOT/artisan 抜粋
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
コマンド処理開始のトリガのようです。ここから先程のhandle() メソッドが呼ばれます。引数はおそらくコマンドラインから渡されたものでしょう。そこは後で追うことになるので今は飛ばします。メイン処理の try の中に $this->bootstrap() が記述されています。 bootstrap() メソッドのはじめで、初期化がされていない場合はアプリケーションコンテナの bootstrapWith() に引数として 先程定義されているのを確認した初期設定用クラスの配列 $bootstrappers を渡しています。アプリケーションコンテナの bootstrapWith() メソッドを見てみましょう。
Illuminate/Foundation/Application::bootstrapWith()
/**
* Run the given array of bootstrap classes.
*
* @param string[] $bootstrappers
* @return void
*/
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]);
}
}
まず、初期化フラグを立てています。先程、初期化がされているか確認するロジックがありましたがそれの判定はここを通過したか否かということのようです。その後に引数で渡された $bootstrappers 配列を foreach で回します。配列の中身を events サービスのdispatch() メソッドに内容を渡しているようです。events サービスは Application クラスの registerCoreContainerAliases() メソッドでエイリアス登録されています。実体は PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php です。呼び出されている dispatch と関係するメソッドを見てみましょう。
Illuminate/Events/Dispatcher::dispatch | 関連メソッド
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
[$event, $payload] = $this->parseEventAndPayload(
$event, $payload
);
if ($this->shouldBroadcast($payload)) {
$this->broadcastEvent($payload[0]);
}
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
/**
* Parse the given event and payload and prepare them for dispatching.
*
* @param mixed $event
* @param mixed $payload
* @return array
*/
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event)) {
[$payload, $event] = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
/**
* Determine if the payload has a broadcastable event.
*
* @param array $payload
* @return bool
*/
protected function shouldBroadcast(array $payload)
{
return isset($payload[0]) &&
$payload[0] instanceof ShouldBroadcast &&
$this->broadcastWhen($payload[0]);
}
/**
* Check if event should be broadcasted by condition.
*
* @param mixed $event
* @return bool
*/
protected function broadcastWhen($event)
{
return method_exists($event, 'broadcastWhen')
? $event->broadcastWhen() : true;
}
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
* @return array
*/
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];
$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
まず、受け取った第一引数の初期化クラス名とイベント名(まずは「bootstrapping: 」)を連結した文字列と第二引数のアプリケーションコンテナを parseEventAndPayload() メソッドに通します。渡されたものがオブジェクトだった場合の処理や配列でない場合の処理をしています。 Arr::wrap() は渡された引数が配列でない場合や null の場合配列にして返すメソッドです。
次に shouldBroadcast()メソッドで第二引数の判定をしています。今回の場合はアプリケーションコンテナが対象になります。shouldBroadcast() メソッドは引数として受け取ったものを配列にキャストして、その0番目がセットされていて、ShouldBroadcast クラスのインスタンスで且つ、broadcastWhen() メソッドが存在しない、若しくは戻り値が true の場合 true そうでない場合 false となります。判定がtrue の場合、 broadcastEvent() メソッドにアプリケーションコンテナを渡します。 今回処理するのはアプリケーションコンテナなのでこの処理は通りません。次の foreach ($this->getListeners($event) as $listener) の処理はイベントリスナとして登録されているかどうかを実装されたインターフェイスも含め調べそれらを foreach で回して処理をしているようです。イベント登録されている場合はトリガが叩かれる感じでしょうか。今回は主題からそれるので別の機会に追いたいと思います。コール元で戻り値を受け取っていないので次に行きましょう。
アプリケーションコンテナの$this->make($bootstrapper)->bootstrap($this) です。配列を回して一つずつインスタンスを生成し、 bootstrap($this) しているようです。サービスを生成し必ず bootstrap() が実行されるという仕様はこの部分で実現されているのでしょう。その後に bootstrapped イベントトリガを叩く手順ですね。
LoadConfiguration
Kernel クラスの初期化で Illuminate\Foundation\Bootstrap\LoadConfiguration がサービスとして初期化されました。このクラスの bootstrap() メソッドが叩かれているはずです。見てみましょう。
Illuminate/Foundation/Bootstrap/LoadConfiguration::bootstrap()
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$items = [];
// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
}
bootstrap() メソッドはアプリケーションコンテナを引数として受け取ります。まず、アプリケーションコンテナの getCachedConfigPath() メソッドを叩き戻り値のファイルが存在確認をしています。Application::getCachedConfigPath() と関連するメソッドは以下のようになっています。
Illuminate\Foundation\Application::Application::getCachedConfigPath()
/**
* Get the path to the configuration cache file.
*
* @return string
*/
public function getCachedConfigPath()
{
return $this->normalizeCachePath('APP_CONFIG_CACHE', 'cache/config.php');
}
/**
* Normalize a relative or absolute path to a cache file.
*
* @param string $key
* @param string $default
* @return string
*/
protected function normalizeCachePath($key, $default)
{
if (is_null($env = Env::get($key))) {
return $this->bootstrapPath($default);
}
return Str::startsWith($env, '/')
? $env
: $this->basePath($env);
}
Application::getCachedConfigPath() は APP_CONFIG_CACHE をキーに、デフォルト値に cache/config.php を引数指定して normalizeCachePath() メソッドをコールしています。normalizeCachePath() は Env::get($key) が null であるか確認しています。
Env クラスは PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Support/Env.php です。 get() メソッドを見てみましょう。
Illuminate\Support\Env::get() | getRepository()
/**
* Get the environment repository instance.
*
* @return \Dotenv\Repository\RepositoryInterface
*/
public static function getRepository()
{
if (static::$repository === null) {
$adapters = array_merge(
[new EnvConstAdapter, new ServerConstAdapter],
static::$putenv ? [new PutenvAdapter] : []
);
static::$repository = RepositoryBuilder::create()
->withReaders($adapters)
->withWriters($adapters)
->immutable()
->make();
}
return static::$repository;
}
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
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([\'"])(.*)\z/', $value, $matches)) {
return $matches[2];
}
return $value;
})
->getOrCall(function () use ($default) {
return value($default);
});
}
static::getRepository()->get($key) からみてみます。 getRepository() メソッドでは static::$repository が null でない場合はそれを返し、 null だった場合は static::$repository を構築する流れのようです。 static::$repository 自体は Dotenv/Repository/RepositoryInterface インターフェイスを実装したインスタンスのようです。処理の中で以下のようなクラスが書かれています。
- EnvConstAdapter
- ServerConstAdapter
- PutenvAdapter
- RepositoryBuilder
これらは、vlucas/phpdotenvを利用したもののようです。Laravel の環境設定を .env に記述するのはこの仕組を利用するためのようです。 Illuminate\Foundation\Console\Kernel で定義した $bootstrappers の一番最初に Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables がありました。これを Kernel::handle() 実行時に読み込んでいます。この LoadEnvironmentVariables::bootstrap() メソッドから createDotenv() メソッドが呼ばれ、 Dotenv::create が実行されています。このメソッドに引数で渡される、環境設定ファイル名 「.env」 とパス情報は アプリケーションコンテナで protected $environmentFile = '.env' と設定されています。 変更したい場合は loadEnvironmentFrom() メソッドで変えられるようです。この phpdotenvでリポジトリを構築することで、Laravel設定ファイル、Apache設定、サーバ環境設定等をにアクセスするインターフェイスを整えてくれるようです。
getRepository() メソッド から RepositoryInterface インタフェースを実装した環境設定情報が返されます。そこから get() メソッドが呼ばれ、PhpOption\Option インターフェイスが実装されたインスタンスが返されます。
PhpOption\Option
PhpOption\Option とは何でしょうか。これは schmittjoh/php-option のようです。 autoload_classmap.php で 'PhpOption\\Option' => $vendorDir . '/phpoption/phpoption/src/PhpOption/Option.php' と定義されています。 Option::fromValue() と関連するクラスは以下のように定義されています。
PhpOption\Option::fromValue()
abstract class Option implements IteratorAggregate
{
/**
* Creates an option given a return value.
*
* This is intended for consuming existing APIs and allows you to easily
* convert them to an option. By default, we treat ``null`` as the None
* case, and everything else as Some.
*
* @template S
*
* @param S $value The actual return value.
* @param S $noneValue The value which should be considered "None"; null by
* default.
*
* @return Option<S>
*/
public static function fromValue($value, $noneValue = null)
{
if ($value === $noneValue) {
return None::create();
}
return new Some($value);
}
/* ========== 略 ========== */
}
PhpOption\None::create()
final class None extends Option
{
/**
* @return None
*/
public static function create()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/* ========== 略 ========== */
}
PhpOption\Some::__construct()
final class Some extends Option
{
/** @var T */
private $value;
/**
* @param T $value
*/
public function __construct($value)
{
$this->value = $value;
}
public function map($callable)
{
return new self($callable($this->value));
}
/* ========== 略 ========== */
}
Option クラスは抽象クラスで、これを静的メソッド呼び出しをしています。Option クラス自体は IteratorAggregate インターフェイスを実装しています。イテレーターとして使うのが目的のようです。渡された第一引数が第二引数と同じ場合は空のオブジェクトである Noneインスタンスを、同じでなければ第一引数を内部に持った Some インスタンスを返すようです。値は Some->value の形で格納され、この値に各種コールできるメソッドを実装しています。今回アクセスしたいのは APP_CONFIG_CACHE なので、$value に APP_CONFIG_CACHE がセットされた Some インスタンスが返ってくるはずです。 Some::map() メソッドの内容は return new self($callable($this->value)) とあります。map() に渡された引数にはクロージャーが入っていました。Some::$value を引数にしたクロージャーの結果を $value に格納した Some インスタンスが返ってくる流れです。そして返された Some インスタンスの getOrCall() メソッドを引数としてクロージャーを入れて呼び出します。ただ、Some::getOrCall() は return $this->value しているだけですので、$value がそのまま返されます。 つまり、アプリケーションコンテナの normalizeCachePath にある if (is_null($env = Env::get($key))) は各環境設定をまとめたリポジトリから APP_CONFIG_CACHE が存在するかを判定しています。もし、存在していな場合は第二引数で指定された $default つまり cache/config.php を引数に bootstrapPath() が呼び出されます。このメソッドは basePath に bootstrap ディレクトリを追加して引数の文字列を連結したものを返します。結果、PROJECT_ROOT/bootstrap/cache/config.php という文字列が返されます。APP_CONFIG_CACHE が存在していた場合は、Str::startsWith($env, '/') が判定され文字列加工をします。startsWith は Java などで使われる関数でPHP関数にないものを独自に定義したもののようです。文字列が引数で指定された文字列で始まるかを判定して true か false を返します。LoadConfiguration::bootstrap() にそのパスが返され、file_exists でそのファイルが存在するか判定され、存在した場合はそのキャッシュファイルを読み込み、読み込みフラグ $loadedFromCache に true がセットされます。 そして、存在していた場合は読み込んだキャッシュを、存在していない場合は空の配列をアプリケーションコンテナに config の名前でバインドします。もし、存在しなかった場合は loadConfigurationFiles() メソッドが第一引数にアプリケーションコンテナ、第二引数に空の配列で初期化した Repository インスタンスを渡して呼ばれます。
Illuminate/Foundation/Bootstrap/LoadConfiguration::loadConfigurationFiles() | 関連メソッド
/**
* Load the configuration items from all of the files.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $repository
* @return void
*
* @throws \Exception
*/
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);
if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}
foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
}
/**
* Get all of the configuration files for the application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return array
*/
protected function getConfigurationFiles(Application $app)
{
$files = [];
$configPath = realpath($app->configPath());
foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
$directory = $this->getNestedDirectory($file, $configPath);
$files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
}
ksort($files, SORT_NATURAL);
return $files;
}
/**
* Get the configuration file nesting path.
*
* @param \SplFileInfo $file
* @param string $configPath
* @return string
*/
protected function getNestedDirectory(SplFileInfo $file, $configPath)
{
$directory = $file->getPath();
if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) {
$nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested).'.';
}
return $nested;
}
loadConfigurationFiles() メソッドのはじめで getConfigurationFiles() がコールされます。 $configPath = realpath($app->configPath()) で設定ファイルのパスをセットしています。これは Application::configPath() で return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path) と定義されていますので、PROJECT_ROOT/config が代入されます。次に foreach で Finder クラスで色々したものを回しています。 Finder クラスは autoload_classmap で /symfony/finder/Finder.php と定義されています。
symfony/finder
symfony/finder/Finderを見てみましょう。
Symfony のディレクトリやファイルの一覧を取得する便利機能が詰め込まれたコンポーネントのようです。見てみましょう。
symfony/finder/Finder 抜粋
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Finder implements \IteratorAggregate, \Countable
{
/* ========== 中略 ========== */
private $names = [];
/* ========== 中略 ========== */
/**
* Creates a new Finder.
*
* @return static
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('*.php')
* $finder->name('/\.php$/') // same as above
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name($patterns)
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $this->normalizeDir($dir);
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR | GLOB_NOSORT)) {
sort($glob);
$resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
} else {
throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, $resolvedDirs);
return $this;
}
}
symfony/finder/Iterator/FileTypeFilterIterator
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileTypeFilterIterator extends \FilterIterator
{
const ONLY_FILES = 1;
const ONLY_DIRECTORIES = 2;
private $mode;
/**
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, int $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/* ========== 略 ========== */
}
クラスドキュメントを見ると、「ファイルとディレクトリを検索するルールを構築できます」とあります。使用例に
$finder = Finder::create()->files()->name('*.php')->in(__DIR__);
とあります。 LoadConfiguration:: getConfigurationFiles() の記述とほぼ同じなので、典型的な利用方法のようです。
まず、create() で static として自分自身を生成して返します。その後使われるメソッドは基本的に戻り値が $this になっていて、メソッドチェーンで処理をすすめる前提になっているようです。 次に files() メソッドがコールされます。 $this->mode に Iterator\FileTypeFilterIterator::ONLY_FILES を代入しています。 ONLY_FILES は 1 が定義されています。ファイルだけ一覧にするモード定数なのでしょう。次に name() メソッドに '*.php' が引数として渡されています。 name() メソッドでは Finder::namesに 渡された引数を array_marge しています。検索条件を配列で蓄える機能と思われます。最後に in() メソッドが設定ファイルのパスを引数としてコールされます。渡された引数を foreach で回し、正規化したパスを Finder::dirs にセットして自身を返します。Finder インスタンスはイテレーターインターフェイスを持っていますので、foreach で回すことができます。つまり、create で生成した後、モードやディレクトリ、その他条件をセットし結果をイテレーターとして提供するコンポーネントですね。では loadConfigurationFiles に戻りましょう。
PROJECT_ROOT/config/*.php 設定ファイルの読み込み
symfony/finder から受け取るものは PROJECT_ROOT/config/ の配下にある .php拡張子のついたファイル一覧だということが先程わかりました。一覧を受け取った後に以下の処理をしています。
Illuminate/Foundation/Bootstrap/LoadConfiguration::getConfigurationFiles() 抜粋
$directory = $this->getNestedDirectory($file, $configPath); $files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();パスの表記を整えてソートした配列を作り変えしています。
loadConfigurationFiles の続きに戻りましょう。
Illuminate/Foundation/Bootstrap/LoadConfiguration::loadConfigurationFiles() 抜粋
if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}
foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
受け取った配列に app をキーとした情報が存在しなければ 「Unable to load the “app” configuration file.」というメッセージを添えた例外を投げています。存在すれば第二引数でうけとったリポジトリインスタンスに設定ファイル名をキーとして、設定ファイルを読み込み、その戻り値の配列をセットして loadConfigurationFiles() メソッドの処理は終了です。 LoadConfiguration::bootstrap() メソッドの続きに戻りましょう。
Illuminate/Foundation/Bootstrap/LoadConfiguration::bootstrap() 抜粋
// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});
Illuminate/Foundation/Application::detectEnvironment()
/**
* Detect the application's current environment.
*
* @param \Closure $callback
* @return string
*/
public function detectEnvironment(Closure $callback)
{
$args = $_SERVER['argv'] ?? null;
return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);
}
detectEnvironment() メソッドにクロージャーを引数として渡しています。クロージャー自体の引数は設定リポジトリが use で指定されています。クロージャーの戻り値は $config->get('app.env', 'production') とあります。ちょうど先程読んだところですね。PROJECT_ROOT/config/app.php に記述されている配列の 「env」 キーを探し、なければ production を返します。 PRODUCT_ROOT/config/app.php には 'env' => env('APP_ENV', 'production') とあるので、 .env ファイルなどで APP_ENV が設定されていればそれを、なければ production を返します。
detectEnvironment() メソッドを読んでみましょう。 EnvironmentDetector インスタンスを生成して、先程のコールバックとスクリプトへ渡された引数の配列を EnvironmentDetector::detect() メソッドに渡しています。 EnvironmentDetector クラスを読んでみましょう。実体は Illuminate/Foundation/EnvironmentDetector です。
Illuminate/Foundation/EnvironmentDetector::detect()
/**
* Detect the application's current environment.
*
* @param \Closure $callback
* @param array|null $consoleArgs
* @return string
*/
public function detect(Closure $callback, $consoleArgs = null)
{
if ($consoleArgs) {
return $this->detectConsoleEnvironment($callback, $consoleArgs);
}
return $this->detectWebEnvironment($callback);
}
/**
* Set the application environment for a web request.
*
* @param \Closure $callback
* @return string
*/
protected function detectWebEnvironment(Closure $callback)
{
return $callback();
}
/**
* Set the application environment from command-line arguments.
*
* @param \Closure $callback
* @param array $args
* @return string
*/
protected function detectConsoleEnvironment(Closure $callback, array $args)
{
// 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.
if (! is_null($value = $this->getEnvironmentArgument($args))) {
return $value;
}
return $this->detectWebEnvironment($callback);
}
/**
* Get the environment argument from the console.
*
* @param array $args
* @return string|null
*/
protected function getEnvironmentArgument(array $args)
{
foreach ($args as $i => $value) {
if ($value === '--env') {
return $args[$i + 1] ?? null;
}
if (Str::startsWith($value, '--env')) {
return head(array_slice(explode('=', $value), 1));
}
}
}
detect() は受け取った第二引数、スクリプトへ渡された引数、つまりコマンドラインから artisan を実行した時の引数の配列の存在を判定し、あった場合は、 detectConsoleEnvironment() メソッドの、なかった場合は detectWebEnvironment() メソッドの戻り値を返します。 detectConsoleEnvironment() メソッドは getEnvironmentArgument() メソッドにコマンド引数の配列を引数として渡します。getEnvironmentArgument() メソッドはコマンド引数を foreach で回し、 --env で指定した値が存在する場合はそれを返します。つまり detect() は artisan コマンドの引数の中に --env があった場合は env を上書します。その結果をアプリケーションコンテナの env に代入します。 LoadConfiguration::bootstrap() の残りはタイムゾーンをセットしてエンコードを UTF-8 に設定して完了です。
最初の目的より少し読みすぎましたが、LoadConfiguration::loadConfigurationFiles() でリポジトリに設定ファイルを読み込みセットしているところを確認しました。Illuminate/Config/Repository::get() で返される Arr::get($this->items, $key, $default) の $this->items の正体が明確になりました。本筋に戻りましょう。