Laravelを知らない中級（中年）プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない！その４：「Larabelアプリケーションの初期化の流れ」


### INDEX
Laravelを知らない中級（中年）プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない！

- [その１「マイグレーションファイルを見てみよう」](https://qiita.com/xioncc/items/1dcec2fbe3485b7e769a)
- [その２「$app['db']って何者？」](https://qiita.com/xioncc/items/cd4a46e5aca16b13174d)
- [その３「Repositoryクラス」](https://qiita.com/xioncc/items/1ce2132594c4596a73ae)
- [その４「Larabelアプリケーションの初期化の流れ」](https://qiita.com/xioncc/items/a17d7f905d4a5abf8103)
- [その５「リポジトリの読込」](https://qiita.com/xioncc/items/dc7df63c2487b02052f3)
- [その６「データベース接続」](https://qiita.com/xioncc/items/3cf9336eecd5f6905831)
- [その７「スキーマビルダー」](https://qiita.com/xioncc/items/04c95ce9ff6d0b7c9d44)
- [その８「migrate コマンドの実行」](https://qiita.com/xioncc/items/4a5166a0963991e33eb5)
- [その９「Kernel::handle()」](https://qiita.com/xioncc/items/07c1f0ba9b40bd230608)



## Larabelアプリケーションの初期化の流れ
Laravelをマイグレーションファイルから少しずつ読み始めて、どうやらお作法であろう存在にいくつか出会いました。そろそろ初期化の流れを追ってみようと思います。

``artisan``が叩かれると、``PROJECT_ROOT/bootstrap/app.php``が呼び出されます。その最初で Illuminate\Foundation\Application が生成されます。

```php: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``です。このクラスのコンストラクタと関連する処理の一部は以下です。

```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());
}
/**
 * Get the path to the application "app" directory.
 *
 * @param  string  $path
 * @return string
 */
public function path($path = '')
{
    $appPath = $this->appPath ?: $this->basePath.DIRECTORY_SEPARATOR.'app';

    return $appPath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
 * Get the base path of the Laravel installation.
 *
 * @param  string  $path Optionally, a path to append to the base path
 * @return string
 */
public function basePath($path = '')
{
    return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
 * Get the base path of the Laravel installation.
 *
 * @param  string  $path Optionally, a path to append to the base path
 * @return string
 */
public function basePath($path = '')
{
    return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
 * Get the path to the language files.
 *
 * @return string
 */
public function langPath()
{
    return $this->resourcePath().DIRECTORY_SEPARATOR.'lang';
}
/**
 * Get the path to the application configuration files.
 *
 * @param  string  $path Optionally, a path to append to the config path
 * @return string
 */
public function configPath($path = '')
{
    return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/* ========== 略 ========== */
```

コンストラクタではまず、パス情報の初期化を行っています。アプリケーションコンテナが生成された時に渡された引数は``$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)``でした。それを引数として``setBasePath()``メソッドを呼び出しています。``setBasePath()``メソッドは渡されたパス情報を``$this->basePath``に格納し、``bindPathsInContainer()``メソッドを呼び出しています。このメソッドではパスの情報をバインドしているようです。``$this->instance()``が何をやっているのか、現段階ではまだ良くわかりません。おそらく後々追うことになるでしょう。今追っている``config``は``path.config``として``PROJECT_ROOT/config``が設定されているようです。

次に``registerBaseBindings()``メソッドが呼び出されています。``registerBaseBindings()``メソッドでは以下の処理が行われています。

```php: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``クラスを見てみましょう。コンストラクタに以下のような定義がされています。

```php: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'``の中で以下のようにアプリケーションコンテナにシングルトンとしてバインドしているようです。生成の流れの詳細はまだわかりません。

```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``に以下のような定義がされています。

```php: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() メソッドの直後に以下のように書かれています。

```php: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()``メソッドを見てみましょう。

```php:Illuminate/Foundation/Application&#x3a;&#x3a;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``と関係するメソッドを見てみましょう。

```php:Illuminate/Events/Dispatcher&#x3a;&#x3a;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()``メソッドに通します。``parseEventAndPayload()``メソッドは、渡されたものがオブジェクトだった場合の処理や配列でない場合の処理をしています。``Arr::wrap()``は渡された引数が配列でない場合や``null``の場合配列にして返す``static``メソッドです。

次に``shouldBroadcast()``メソッドで``dispatch()``メソッドに渡された第二引数の判定をしています。今回の場合は**アプリケーションコンテナ**が対象になります。``shouldBroadcast()``メソッドは引数として受け取ったものを配列アクセスし、０番目がセットされていてそれが``ShouldBroadcast``クラスのインスタンスで且つ、``broadcastWhen()``メソッドが存在しない、若しくは戻り値が``true``の場合``true``に、そうでない場合``false``になります。
判定が``true``の場合、``broadcastEvent()``メソッドに渡します。 今回処理するのはアプリケーションコンテナなのでこの処理は通りません。次の``foreach ($this->getListeners($event) as $listener)``の処理はイベントリスナとして登録されているかどうかを実装されたインターフェイスも含め調べそれらを``foreach``で回して処理をしているようです。イベント登録されている場合はトリガが叩かれる感じでしょうか。今回は主題からそれるので別の機会に追いたいと思います。コール元で戻り値を受け取っていないので次に行きましょう。

アプリケーションコンテナの``$this->make($bootstrapper)->bootstrap($this)``です。``$bootstrappers``配列を回して一つずつインスタンスを生成し、``bootstrap($this)``しているようです。サービスを生成し必ず``bootstrap()``メソッドが実行されるという仕様はこの部分で実現されているのでしょう。その後に``bootstrapped``イベントトリガを叩く手順ですね。

### LoadConfiguration
``Kernel``クラスの初期化で``$bootstrappers``配列に含まれていた``Illuminate\Foundation\Bootstrap\LoadConfiguration``がサービスとして初期化されました。このクラスの``bootstrap()``メソッドが叩かれているはずです。見てみましょう。

```php:Illuminate/Foundation/Bootstrap/LoadConfiguration&#x3a;&#x3a;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()``と関連するメソッドは以下のようになっています。

```php:Illuminate\Foundation\Application&#x3a;&#x3a;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()``メソッドを見てみましょう。

```php:Illuminate\Support\Env&#x3a;&#x3a;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([\'"])(.*)\1\z/', $value, $matches)) {
                return $matches[2];
            }
            return $value;
        })
        ->getOrCall(function () use ($default) {
            return value($default);
        });
}
```

``Option::fromValue()``に引数として渡している``static::getRepository()->get($key)``から見てみます。``getRepository()``メソッドでは``static::$repository``が``null``でない場合はそれを返し、``null``だった場合は``static::$repository``を構築する流れのようです。``static::$repository``自体は``Dotenv/Repository/RepositoryInterface``インターフェイスを実装したインスタンスのようです。処理の中で以下のようなクラスが書かれています。

* EnvConstAdapter
* ServerConstAdapter
* PutenvAdapter
* RepositoryBuilder

これらは、[vlucas/phpdotenv](https://github.com/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](https://github.com/schmittjoh/php-option/blob/master/src/PhpOption/Option.php)のようです。``autoload_classmap.php``で``'PhpOption\\Option' => $vendorDir . '/phpoption/phpoption/src/PhpOption/Option.php'``と定義されています。``Option::fromValue()``と関連するクラスは以下のように定義されています。

```php:PhpOption\Option&#x3a;&#x3a;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);
}
/* ========== 略 ========== */
}
```

```php:PhpOption\None&#x3a;&#x3a;create()
final class None extends Option
{
/**
 * @return None
 */
public static function create()
{
    if (null === self::$instance) {
        self::$instance = new self();
    }
    return self::$instance;
}
/* ========== 略 ========== */
}
```

```php:PhpOption\Some&#x3a;&#x3a;__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``インスタンスを渡してコールします。

```php:Illuminate/Foundation/Bootstrap/LoadConfiguration&#x3a;&#x3a;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``のディレクトリやファイルの一覧を取得する便利機能が詰め込まれたコンポーネントのようです。見てみましょう。

```php: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;
}
}
```

```php: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);
    }
/* ========== 略 ========== */
}
```

クラスドキュメントを見ると、「ファイルとディレクトリを検索するルールを構築できます」とあります。使用例に

```php
$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``拡張子のついたファイル一覧だということが先程わかりました。一覧を受け取った後に以下の処理をしています。

```php:Illuminate/Foundation/Bootstrap/LoadConfiguration&#x3a;&#x3a;getConfigurationFiles()抜粋
$directory = $this->getNestedDirectory($file, $configPath);
$files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
```

パスの表記を整えてソートした配列を作り変えしています。``loadConfigurationFiles``の続きに戻りましょう。

```php:Illuminate/Foundation/Bootstrap/LoadConfiguration&#x3a;&#x3a;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()``メソッドの続きに戻りましょう。

```php:Illuminate/Foundation/Bootstrap/LoadConfiguration&#x3a;&#x3a;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');
});
```

```php:Illuminate/Foundation/Application&#x3a;&#x3a;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``です。

```php:Illuminate/Foundation/EnvironmentDetector&#x3a;&#x3a;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``の正体が明確になりました。

## 次回
初期化の流れを読んでみてなんとなく、ふんわりやってることがわかってきましたが、まだオブジェクトの生成やイベント周りなどはっきりとつかめていない部分がありますね。徐々に理解していけると良いですが、どうなるでしょうか。次回はこの流れでリポジトリの読込を探っていきたいと思います。

続く☆