Laravelを知らない中級（中年）プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない！その５ 「リポジトリの読込」

### 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アプリケーションの初期化の流れ」を見てきました。流れでリポジトリの読込について読んでみましょう！
``$app['db']``が生成される時の流れをもう一度見てみましょう。コマンドラインから``artisan``コマンドが叩かれると、``Illumination/Foundation/Console/Kernel::$bootstrappers[]``配列に登録されたサービスがアプリケーションコンテナで読み込まれます。この中に``Illumination/Foundation/Bootstrap/RegisterProviders``が含まれています。この``RegisterProviders``の``bootstrap()``メソッドには``$app->registerConfiguredProviders()``とあります。``Application::registerConfiguredProviders()``は以下が定義されています。

```php:Application&#x3a;&#x3a;registerConfiguredProviders()
/**
 * Register all of the configured providers.
 *
 * @return void
 */
public function registerConfiguredProviders()
{
    $providers = Collection::make($this->config['app.providers'])
                    ->partition(function ($provider) {
                        return strpos($provider, 'Illuminate\\') === 0;
                    });
    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}
```

``Collection::make()``に``$this->config['app.providers']``つまり``PROJECT_ROOT/config/app.php``に記述されている配列の``providers``を渡しています。``Collection``クラスは``Illuminate/Support/Collection``ですが、このクラス自体には``make()``は実装されていません。このメソッドは``use EnumeratesValues``されているトレイトの``Illuminate/Support/Traits/EnumeratesValues``に以下のように定義されています。

```php:Illuminate/Support/Traits/EnumeratesValues&#x3a;&#x3a;make()|getArrayableItems()
/**
 * Create a new collection instance if the value isn't one already.
 *
 * @param  mixed  $items
 * @return static
 */
public static function make($items = [])
{
    return new static($items);
}
/**
 * Results array of items from Collection or Arrayable.
 *
 * @param  mixed  $items
 * @return array
 */
protected function getArrayableItems($items)
{
    if (is_array($items)) {
        return $items;
    } elseif ($items instanceof Enumerable) {
        return $items->all();
    } elseif ($items instanceof Arrayable) {
        return $items->toArray();
    } elseif ($items instanceof Jsonable) {
        return json_decode($items->toJson(), true);
    } elseif ($items instanceof JsonSerializable) {
        return (array) $items->jsonSerialize();
    } elseif ($items instanceof Traversable) {
        return iterator_to_array($items);
    }
    return (array) $items;
}
```

```php:Illuminate/Support/Collection&#x3a;&#x3a;__construct()
/**
 * Create a new collection.
 *
 * @param  mixed  $items
 * @return void
 */
public function __construct($items = [])
{
    $this->items = $this->getArrayableItems($items);
}
```

渡された``config['app.providers']``は配列でした。これを``Collection::__construct()``に引数として渡し生成したインスタンスを戻しています。コンストラクタは受け取った引数を``$this->items に getArrayableItems``メソッドを通して格納しています。この``Collection``クラスは``Enumerable``インターフェイスを継承しています。``Illuminate/Support/Enumerable``クラスは複数のインターフェイスを実装しています。

```php
interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
```

様々な形でデータを扱えるようです。その情報を配列の形に変換する役割を担うのが``EnumeratesValues::getArrayableItems``です。

``Application::registerConfiguredProviders()``の続きです。``Collection::make()``の戻り値にメソッドチェーンで``partition()``メソッドをクロージャーを引数にコールしています。これはトレイトの``Illuminate/Support/Traits/EnumeratesValues``で定義されています。

```php:Illuminate/Support/Traits/EnumeratesValues&#x3a;&#x3a;partition()|関連メソッド
/**
 * Partition the collection into two arrays using the given callback or key.
 *
 * @param  callable|string  $key
 * @param  mixed  $operator
 * @param  mixed  $value
 * @return static
 */
public function partition($key, $operator = null, $value = null)
{
    $passed = [];
    $failed = [];
    $callback = func_num_args() === 1
            ? $this->valueRetriever($key)
            : $this->operatorForWhere(...func_get_args());
    foreach ($this as $key => $item) {
        if ($callback($item, $key)) {
            $passed[$key] = $item;
        } else {
            $failed[$key] = $item;
        }
    }
    return new static([new static($passed), new static($failed)]);
}
/**
 * Determine if the given value is callable, but not a string.
 *
 * @param  mixed  $value
 * @return bool
 */
protected function useAsCallable($value)
{
    return ! is_string($value) && is_callable($value);
}
/**
 * Get a value retrieving callback.
 *
 * @param  callable|string|null  $value
 * @return callable
 */
protected function valueRetriever($value)
{
    if ($this->useAsCallable($value)) {
        return $value;
    }
    return function ($item) use ($value) {
        return data_get($item, $value);
    };
}
/**
 * Get an operator checker callback.
 *
 * @param  string  $key
 * @param  string|null  $operator
 * @param  mixed  $value
 * @return \Closure
 */
protected function operatorForWhere($key, $operator = null, $value = null)
{
    if (func_num_args() === 1) {
        $value = true;
        $operator = '=';
    }
    if (func_num_args() === 2) {
        $value = $operator;
        $operator = '=';
    }
    return function ($item) use ($key, $operator, $value) {
        $retrieved = data_get($item, $key);
        $strings = array_filter([$retrieved, $value], function ($value) {
            return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
        });
        if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
            return in_array($operator, ['!=', '<>', '!==']);
        }
        switch ($operator) {
            default:
            case '=':
            case '==':  return $retrieved == $value;
            case '!=':
            case '<>':  return $retrieved != $value;
            case '<':   return $retrieved < $value;
            case '>':   return $retrieved > $value;
            case '<=':  return $retrieved <= $value;
            case '>=':  return $retrieved >= $value;
            case '===': return $retrieved === $value;
            case '!==': return $retrieved !== $value;
        }
    };
}
```

``partition()``に渡した第一引数はクロージャーでした。メソッドのPHPDocはストリングも受け入ると定義されています。おそらくこれを解決するロジックが挟まれるでしょう。メソッドの引数の数を検証し、１ならば``valueRetriever()``をコールしています。コール先では、渡された引数が使用可能か検証しています。問題なく使用可能ならそのまま戻します。使用できない場合（おそらくストリング型）は``data_get()``を通して戻しています。この``data_get()``ですが、今までのコードで定義を見かけていません。これは、Laravel が用意している「ヘルパ」関数です。実体は``Illuminate/Support/helpers.php``です。プロジェクト内でどこからでも使える便利関数的なものでしょう。こちらはまた別の機会に追ってみたいと思います。この関数自体の挙動は、「ドット」記法を使用し、ネストした配列やオブジェクトから値を取得するものです。``Collection``インスタンスの情報にドットシンタックスでアクセスするためのものと推測できます。次は``partition``の引数の数が１出なかった場合です。``operatorForWhere()``を``partition()``に渡された引数をsplat演算子で引数としてコールしています。``operatorForWhere()``を見てみましょう。
引数が１つ若しくは２つだった時の挙動を整理して、引数で渡されたキーと演算子と値でフィルタした結果を返すクロージャーを戻します。
``partition()``の続きに戻ります。``Collection``クラスは``IteratorAggregate``を継承しているので``foreach``で回すことができます。自身を``foreach``で回します。先程整えたコールバック関数で内容を検証し、``true``と``false``で選別して配列を生成し、各情報を格納した``Collection``クラスを配列として格納した``Collection``クラスを戻します。

``Application::registerConfiguredProviders()``の続きです。

```php
$providers = Collection::make($this->config['app.providers'])
                ->partition(function ($provider) {
                    return strpos($provider, 'Illuminate\\') === 0;
                });
```

``PROJECT_ROOT/config/app.php``に記述された配列の``providers``キーの内容の配列を``foreach``で回し、``strpos($provider, 'Illuminate\\') === 0``を判定して結果ごとに``Collection``を生成して``$providers``に代入するということのようです。
次に行きましょう。

```php
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
```

先程の結果で生成された``Collection``クラスの``splice()``メソッドをコールしています。

```php:Illumination/Support/Collection&#x3a;&#x3a;splice()
/**
 * Splice a portion of the underlying collection array.
 *
 * @param  int  $offset
 * @param  int|null  $length
 * @param  mixed  $replacement
 * @return static
 */
public function splice($offset, $length = null, $replacement = [])
{
    if (func_num_args() === 1) {
        return new static(array_splice($this->items, $offset));
    }
    return new static(array_splice($this->items, $offset, $length, $replacement));
}
```

引数が１つの場合、``$offset``で指定された場所以降の情報を削除した情報をもつ``Collection``インスタンスを返します。
一つ以上の場合は``array_splice``関数どおりの処理で生成された配列情報を持つ``Collection``インスタンスを返します。
今回は、``PROJECT_ROOT/config/app.php``に記述された配列の``providers``キーの内容で、``Illuminate\\``で始まるもの以外を削除し、``$this->make(PackageManifest::class)->providers()``を追加した``Collection``インスタンスを返します。アプリケーションコンテナにバインドされた``PackageManifest::providers()``の戻り値は``return $this->config('providers')``となっています。リポジトリに登録された``providers``の設定値を追加しています。

```php
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))->load($providers->collapse()->toArray());
```

``Illuminate/Foundation/ProviderRepository``を生成して``load()``メソッドをコールしています。

```php:Illuminate/Foundation/ProviderRepository&#x3a;&#x3a;load()|関連メソッド
/**
 * Register the application service providers.
 *
 * @param  array  $providers
 * @return void
 */
public function load(array $providers)
{
    $manifest = $this->loadManifest();
    // First we will load the service manifest, which contains information on all
    // service providers registered with the application and which services it
    // provides. This is used to know which services are "deferred" loaders.
    if ($this->shouldRecompile($manifest, $providers)) {
        $manifest = $this->compileManifest($providers);
    }
    // Next, we will register events to load the providers for each of the events
    // that it has requested. This allows the service provider to defer itself
    // while still getting automatically loaded when a certain event occurs.
    foreach ($manifest['when'] as $provider => $events) {
        $this->registerLoadEvents($provider, $events);
    }
    // We will go ahead and register all of the eagerly loaded providers with the
    // application so their services can be registered with the application as
    // a provided service. Then we will set the deferred service list on it.
    foreach ($manifest['eager'] as $provider) {
        $this->app->register($provider);
    }
    $this->app->addDeferredServices($manifest['deferred']);
}
/**
 * Load the service provider manifest JSON file.
 *
 * @return array|null
 */
public function loadManifest()
{
    // The service manifest is a file containing a JSON representation of every
    // service provided by the application and whether its provider is using
    // deferred loading or should be eagerly loaded on each request to us.
    if ($this->files->exists($this->manifestPath)) {
        $manifest = $this->files->getRequire($this->manifestPath);
        if ($manifest) {
            return array_merge(['when' => []], $manifest);
        }
    }
}
/**
 * Determine if the manifest should be compiled.
 *
 * @param  array  $manifest
 * @param  array  $providers
 * @return bool
 */
public function shouldRecompile($manifest, $providers)
{
    return is_null($manifest) || $manifest['providers'] != $providers;
}
/**
 * Register the load events for the given provider.
 *
 * @param  string  $provider
 * @param  array  $events
 * @return void
 */
protected function registerLoadEvents($provider, array $events)
{
    if (count($events) < 1) {
        return;
    }
    $this->app->make('events')->listen($events, function () use ($provider) {
        $this->app->register($provider);
    });
}
/**
 * Compile the application service manifest file.
 *
 * @param  array  $providers
 * @return array
 */
protected function compileManifest($providers)
{
    // The service manifest should contain a list of all of the providers for
    // the application so we can compare it on each request to the service
    // and determine if the manifest should be recompiled or is current.
    $manifest = $this->freshManifest($providers);
    foreach ($providers as $provider) {
        $instance = $this->createProvider($provider);
        // When recompiling the service manifest, we will spin through each of the
        // providers and check if it's a deferred provider or not. If so we'll
        // add it's provided services to the manifest and note the provider.
        if ($instance->isDeferred()) {
            foreach ($instance->provides() as $service) {
                $manifest['deferred'][$service] = $provider;
            }
            $manifest['when'][$provider] = $instance->when();
        }
        // If the service providers are not deferred, we will simply add it to an
        // array of eagerly loaded providers that will get registered on every
        // request to this application instead of "lazy" loading every time.
        else {
            $manifest['eager'][] = $provider;
        }
    }
    return $this->writeManifest($manifest);
}
/**
 * Create a fresh service manifest data structure.
 *
 * @param  array  $providers
 * @return array
 */
protected function freshManifest(array $providers)
{
    return ['providers' => $providers, 'eager' => [], 'deferred' => []];
}
/**
 * Write the service manifest file to disk.
 *
 * @param  array  $manifest
 * @return array
 *
 * @throws \Exception
 */
public function writeManifest($manifest)
{
    if (! is_writable($dirname = dirname($this->manifestPath))) {
        throw new Exception("The {$dirname} directory must be present and writable.");
    }
    $this->files->replace(
        $this->manifestPath, '<!--?php return '.var_export($manifest, true).';'
    );
    return array_merge(['when' =--> []], $manifest);
}
/**
 * Create a new provider instance.
 *
 * @param  string  $provider
 * @return \Illuminate\Support\ServiceProvider
 */
public function createProvider($provider)
{
    return new $provider($this->app);
}
```

まず、``__construct()``の第三引数として渡された``$this->getCachedServicesPath()``のパスにキャッシュファイルがあるか``loadManifest()``で判定し、存在していた場合読み込みます。
``shouldRecompile()``はキャッシュの正当性を判定します。キャッシュが存在しない、若しくは``load()``に引数として渡されたプロバイダーがキャッシュと等価でない場合は、``compileManifest()``で``$manifest``を構築します。
``compileManifest()``は``freshManifest``で``$manifest``を初期化して、引数として受け取ったプロバイダー情報を``foreach``で回し、一つずつ``createProvider()``でインスタンス生成し、そのインスタンスが遅延ロードか否かを``$manifest``に登録します。そして構築できたものを``writeManifest()``でファイルとして書き出します。おそらく構築したものをキャッシュしてファイルへのアクセス回数を減らす高速化のための処理でしょう。
次に``$manifest['when']``を``foreach``で回し、``registerLoadEvents()``をコールしてプロバイダがどのタイミングで``Application::register()``に渡されるのか登録します。
そして、``$manifest['eager']``を``foreach``で回して、初期化時に必要なプロバイダーを登録します。
最後にアプリケーションコンテナに遅延ロードするプロバイダーを登録します。
この``providers``に登録されたサービスは、実際に``make``される時は、``Application::registerCoreContainerAliases()``でエイリアス登録されたものを、``Container::getAlias()``で名前解決しビルドされます。

以上の処理で、``PROJECT_ROOT/config/app.php``の``providers``に記述されているサービスを読み込む流れがわかりました。

## 次回
少しだけ謎が解けてきましたね！いい感じです。ちょっとずつ全体がわかるといいですね！
次回はデータベース接続について読んでみたいと思います。そろそろわかるようになっているかもしれません！

続く☆