XIONCC

【フレームワークを読む】Laravelのマイグレーションファイルは何をしているのか?:その1

Larabelの artisanmake:migration で生成されたマイグレーションファイルは何をしているのかソースコードを読んでみます。
バージョンは 7.2.0 のものです。
【前提】

私は Larabel のクイックスタートを数回読んだくらいで全く利用経験が無いため、使い方を知るためにソースコードを読むことにしました。この記事の対象は PHP 中級者でLaravel未経験者及び初心者を想定しています。スクリプトが叩かれた箇所からなぞるように読んだものを記しているので説明の順序が通常の紹介記事とくらべると分かりづらい部分があるかもしれません。ご容赦ください。


マイグレーションファイルの生成

以下のコマンドを叩き customers テーブルを定義するためのマイグレーションファイルを生成します。

COMMAND LINE

$ php artisan make:migration create_customers_table --create=customers
Created Migration: 2020_03_23_050035_create_customers_table

PROJECT_ROOT/database/migrations/2020_03_23_050035_create_customers_table.php として、以下のようなファイルが生成されました。

生成されたファイル

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCustomersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('customers');
    }
}

フレームワークのクラスファイルの配置場所

以下の3つの名前空間が use で宣言されています。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Laravelのパッケージは Illuminate という名前空間の下で展開されているようです。
これの実体は、 PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/ 以下にあります。
PROJECT_ROOT/vendor/laravel/framework/composer.json があるので目を通しておくと良いかもしれません。

Migrationクラス

生成された CreateCustomersTable クラスは Migration 抽象クラスを継承しています。
Migration 抽象クラスは以下のように定義されています。

Illuminate/Database/Migrations/Migration

<?php

namespace Illuminate\Database\Migrations;

abstract class Migration
{
    /**
     * The name of the database connection to use.
     *
     * @var string|null
     */
    protected $connection;

    /**
     * Enables, if supported, wrapping the migration within a transaction.
     *
     * @var bool
     */
    public $withinTransaction = true;

    /**
     * Get the migration connection name.
     *
     * @return string|null
     */
    public function getConnection()
    {
        return $this->connection;
    }
}

データベース接続名 $connection と トランザクションを使用するかどうかの $withinTransaction (デフォルト:true) の2つの変数と、単純に$connectionが返されるgetConnectionメソッドがが宣言されています。

Schemaクラス

CreateCustomersTable クラスは Migration 抽象クラスに up()down() の2つのメソッドを追加しています。どちらも Schema クラスのstaticメソッドを呼び出しています。Schema クラスは PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php で以下のように定義されています。

Illuminate/Support/Facades/Schema

<?php

namespace Illuminate\Support\Facades;

/**
 * @method static \Illuminate\Database\Schema\Builder create(string $table, \Closure $callback)
 * @method static \Illuminate\Database\Schema\Builder drop(string $table)
 * @method static \Illuminate\Database\Schema\Builder dropIfExists(string $table)
 * @method static \Illuminate\Database\Schema\Builder table(string $table, \Closure $callback)
 * @method static \Illuminate\Database\Schema\Builder rename(string $from, string $to)
 * @method static void defaultStringLength(int $length)
 * @method static bool hasTable(string $table)
 * @method static bool hasColumn(string $table, string $column)
 * @method static bool hasColumns(string $table, array $columns)
 * @method static \Illuminate\Database\Schema\Builder disableForeignKeyConstraints()
 * @method static \Illuminate\Database\Schema\Builder enableForeignKeyConstraints()
 * @method static void registerCustomDoctrineType(string $class, string $name, string $type)
 *
 * @see \Illuminate\Database\Schema\Builder
 */
class Schema extends Facade
{
    /**
     * Get a schema builder instance for a connection.
     *
     * @param  string|null  $name
     * @return \Illuminate\Database\Schema\Builder
     */
    public static function connection($name)
    {
        return static::$app['db']->connection($name)->getSchemaBuilder();
    }

    /**
     * Get a schema builder instance for the default connection.
     *
     * @return \Illuminate\Database\Schema\Builder
     */
    protected static function getFacadeAccessor()
    {
        return static::$app['db']->connection()->getSchemaBuilder();
    }
}

Facade抽象クラス

Schema クラスは Facade 抽象クラスを継承して connectiongetFacadeAccessor の2つのメソッドを定義しています。呼び出している createdropIfExists は見当たりません。Facade 抽象クラスを見てみましょう。PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php で定義されいてます。少しコードが長いので中略します。

Illuminate/Support/Facades/Facade

<?php

namespace Illuminate\Support\Facades;

use Closure;
use Mockery;
use Mockery\MockInterface;
use RuntimeException;

abstract class Facade
{
    /**
     * The application instance being facaded.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected static $app;

    /**
     * The resolved object instances.
     *
     * @var array
     */
    protected static $resolvedInstance;

 
    /* ========== 中略 ========== */


    /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    /**
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  object|string  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        if (static::$app) {
            return static::$resolvedInstance[$name] = static::$app[$name];
        }
    }

 
    /* ========== 中略 ========== */



    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array  $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
}

ざっと見た感じ、呼び出したいメソッドは見当たりません。最下部にマジックメソッド __callStatic が定義されています。アクセス不能メソッドを静的コンテキストで実行したときに起動するものです。おそらくこれが叩かれるのでしょう。第一引数はコールしたメソッド名、第二引数は渡されたパラメータが配列の形で格納されています。 __callStatic のはじめに自身の getFacadeRoot() を呼び出して戻り値を $instance に格納しています。getFacadeRoot() は自身の resolveFacadeInstance() に 自身の getFacadeAccessor() を引数として渡しています。少しややこしくなってきましたね。一つずつ見ましょう。getFacadeAccessor() は継承された Schema クラスでオーバーライドされています。

Illuminate/Support/Facades/Schema::getFacadeAccessor()

/**
 * Get a schema builder instance for the default connection.
 *
 * @return \Illuminate\Database\Schema\Builder
 */
protected static function getFacadeAccessor()
{
    return static::$app['db']->connection()->getSchemaBuilder();
}

上書きされなかった場合、例外 RuntimeException が “Facade does not implement getFacadeAccessor method.” というメッセージで投げられるように抽象クラスで定義されています。

Illuminate/Support/Facades/Facade::getFacadeAccessor()

/**
 * Get the registered name of the component.
 *
 * @return string
 *
 * @throws \RuntimeException
 */
protected static function getFacadeAccessor()
{
    throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}

$app[‘db’]について

Schema::getFacadeAccessor() から返される戻り値 static::$app['db']->connection()->getSchemaBuilder() を見ていきましょう。

順序が逆ですが、マイグレーションファイルを利用する時、artisanmigrate を利用します。artisan 自体の考察は範囲が広くなってしまうので別の機会にしたいと思いますが、static::$app['db'] について観察するために artisan を少し覗いてみます。このコマンドの実体は PROJECT_ROOT/artisanです。以下のような内容が記述されています。

PROJECT_ROOT/artisan

#!/usr/bin/env php
<?php

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/

require __DIR__.'/vendor/autoload.php';

$app = require_once __DIR__.'/bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/

$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);

$status = $kernel->handle(
    $input = new Symfony\Component\Console\Input\ArgvInput,
    new Symfony\Component\Console\Output\ConsoleOutput
);

/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/

$kernel->terminate($input, $status);

exit($status);

前半で、シバンで環境パスのPHP実行ファイルを指定して、起動タイムスタンプを LARAVEL_START 定数にセットし、composer のオートロードを読み込み、 $appPROJECT_ROOT/bootstrap/app.php の戻り値を格納しています。app.php の内容は以下です。

PROJECT_ROOT/bootstrap/app.php

<?php

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/

return $app;

最初に Illuminate\Foundation\Application を生成し $app に格納しています。その後にサービスコンテナにシングルトンでいくつかサービスを入れ、戻り値として $app を返しています。サービスコンテナについては長くなりますので別の機会に考察します。
$appApplication クラスで Container クラスを継承しています。Container クラスは PROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Container/Container.php で定義されています。このクラスは ArrayAccess をインターフェースとして実装しています。ArrayAccess は配列としてオブジェクトにアクセスするための機能のインターフェイスです。概要は以下です。(php.netから引用)

ArrayAccess インターフェイス概要

ArrayAccess {
/* メソッド */
abstract public offsetExists ( mixed $offset ) : bool
abstract public offsetGet ( mixed $offset ) : mixed
abstract public offsetSet ( mixed $offset , mixed $value ) : void
abstract public offsetUnset ( mixed $offset ) : void
}

Container::offsetGet と関連するメソッドは以下のように定義されています。

Illuminate/Container/Container::offsetGet() | 関連メソッド

/**
 * Get the value at a given offset.
 *
 * @param  string  $key
 * @return mixed
 */
public function offsetGet($key)
{
    return $this->make($key);
}
/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}
/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @param  bool  $raiseEvents
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
        $this->getContextualConcrete($abstract)
    );

    // If an instance of the type is currently being managed as a singleton we'll
    // just return an existing instance instead of instantiating new instances
    // so the developer can keep using the same objects instance every time.
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    $concrete = $this->getConcrete($abstract);

    // We're ready to instantiate an instance of the concrete type registered for
    // the binding. This will instantiate the types, as well as resolve any of
    // its "nested" dependencies recursively until all have gotten resolved.
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }

    // If we defined any extenders for this type, we'll need to spin through them
    // and apply them to the object being built. This allows for the extension
    // of services, such as changing configuration or decorating the object.
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    // If the requested type is registered as a singleton we'll want to cache off
    // the instances in "memory" so we can return it later without creating an
    // entirely new instance of an object on each subsequent request for it.
    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    if ($raiseEvents) {
        $this->fireResolvingCallbacks($abstract, $object);
    }

    // Before returning, we will also set the resolved flag to "true" and pop off
    // the parameter overrides for this build. After those two things are done
    // we will be ready to return back the fully constructed class instance.
    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
}
/**
 * Instantiate a concrete instance of the given type.
 *
 * @param  string  $concrete
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }

    try {
        $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
        throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
    }

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface or Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    // If there are no constructors, that means there are no dependencies then
    // we can just resolve the instances of the objects right away, without
    // resolving any other types or dependencies out of these containers.
    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }

    $dependencies = $constructor->getParameters();

    // Once we have all the constructor's parameters we can create each of the
    // dependency instances and then use the reflection instances to make a
    // new instance of this class, injecting the created dependencies in.
    try {
        $instances = $this->resolveDependencies($dependencies);
    } catch (BindingResolutionException $e) {
        array_pop($this->buildStack);

        throw $e;
    }

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

少し長いですね。ざっくり追いましょう。ArrayAccess インターフェイスを実装しているので、配列としてオブジェクトにアクセスすると、offsetGet() の戻り値が返されます。つまり、return $this->make($key) が実行されます。make() メソッドには return $this->resolve($abstract, $parameters) と記されています。つまり、resolve() メソッドが主な処理です。いろいろ書かれています。
$abstract = $this->getAlias($abstract)で生成したいクラスの名前解決を行う。
シングルトンクラスの場合、それを保証するためすでにインスタンスがある場合それを返す。依存性を確認して他のクラスが必要だった場合再帰的に処理を行う。依存性が解決したら build() メソッドを呼び出す。生成する前に ReflectionClass でクラスの情報を取得し正常に生成できるか確認し、問題なければ生成して返される。というのが大まかな流れのようです。
名前解決についてですが、$this->aliases 配列の内容が返されます。これは、Application クラスのコンストラクタで呼び出している Application::registerCoreContainerAliases()メソッドでセットされます。内容は以下のようなものです。

Illuminate/Foundation/Application::registerCoreContainerAliases()

/**
 * Register the core class aliases in the container.
 *
 * @return void
 */
public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
        'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
        'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
        'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
        'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
        'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
        'cache.psr6'           => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
        'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
        'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
        'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
        'db'                   => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
        'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
        'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
        'files'                => [\Illuminate\Filesystem\Filesystem::class],
        'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
        'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
        'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
        'hash'                 => [\Illuminate\Hashing\HashManager::class],
        'hash.driver'          => [\Illuminate\Contracts\Hashing\Hasher::class],
        'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
        'log'                  => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
        'mail.manager'         => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class],
        'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
        'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
        'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
        'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
        'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
        'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
        'redirect'             => [\Illuminate\Routing\Redirector::class],
        'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
        'redis.connection'     => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
        'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
        'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
        'session'              => [\Illuminate\Session\SessionManager::class],
        'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
        'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
        'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
        'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

随分あちこち回りましたが、つまり Schema::getFacadeAccessorstatic::$app['db']Illuminate\Database\ConnectionResolverInterface を実装した Illuminate\Database\DatabaseManagerのインスタンスのことのようです。static::$app['db']->connection() を見てみましょう。Illuminate\Database\DatabaseManager にあるはずです。

DatabaseManagerクラス

DatabaseManager::connection のと関連するメソッドの定義内容は以下です。

Illuminate/Database/DatabaseManager::connection() | 関連メソッド

/**
 * Get a database connection instance.
 *
 * @param  string|null  $name
 * @return \Illuminate\Database\Connection
 */
public function connection($name = null)
{
    [$database, $type] = $this->parseConnectionName($name);

    $name = $name ?: $database;

    // If we haven't created this connection, we'll create it based on the config
    // provided in the application. Once we've created the connections we will
    // set the "fetch mode" for PDO which determines the query return types.
    if (! isset($this->connections[$name])) {
        $this->connections[$name] = $this->configure(
            $this->makeConnection($database), $type
        );
    }

    return $this->connections[$name];
}
/**
 * Parse the connection into an array of the name and read / write type.
 *
 * @param  string  $name
 * @return array
 */
protected function parseConnectionName($name)
{
    $name = $name ?: $this->getDefaultConnection();

    return Str::endsWith($name, ['::read', '::write'])
                        ? explode('::', $name, 2) : [$name, null];
}
/**
 * Get the default connection name.
 *
 * @return string
 */
public function getDefaultConnection()
{
    return $this->app['config']['database.default'];
}

connection()で、まずparseConnectionName($name) が呼び出されます。
parseConnectionName($name) では、$name が空なら getDefaultConnection() が呼ばれ、$this->app['config']['database.default']$name セットされます。
$this->app['config']['database.default'] を追ってみましょう。

Repositoryクラス

$this->app['config'] は記事の少し前の方で触れた ArrayAccess ですね。実体は Illuminate\Contracts\Config\Repository インターフェースを実装した Illuminate\Config\Repository です。

Illuminate/Config/Repository 抜粋

<?php

namespace Illuminate\Config;

use ArrayAccess;
use Illuminate\Contracts\Config\Repository as ConfigContract;
use Illuminate\Support\Arr;

class Repository implements ArrayAccess, ConfigContract
{
/**
 * All of the configuration items.
 *
 * @var array
 */
protected $items = [];

/**
 * Create a new configuration repository.
 *
 * @param  array  $items
 * @return void
 */
public function __construct(array $items = [])
{
    $this->items = $items;
}
/* ========== 中略 ========== */
/**
 * Get the specified configuration value.
 *
 * @param  array|string  $key
 * @param  mixed  $default
 * @return mixed
 */
public function get($key, $default = null)
{
    if (is_array($key)) {
        return $this->getMany($key);
    }

    return Arr::get($this->items, $key, $default);
}
/* ========== 中略 ========== */
/**
 * Get a configuration option.
 *
 * @param  string  $key
 * @return mixed
 */
public function offsetGet($key)
{
        return $this->get($key);
}
/* ========== 中略 ========== */
}

このクラスも ArrayAccess が実装されています。 offsetGetreturn $this->get($key); と定義されています。配列で渡された時の処理を挟んでいますが、実行するのは return Arr::get($this->items, $key, $default)のようです。 Arrを見てみましょう。

Arr::get

Arrクラスの定義ファイルはIlluminate/Support/Arr.php です。get メソッドは以下のように定義されています。

Illuminate/Support/Arr::get()

/**
 * Get an item from an array using "dot" notation.
 *
 * @param  \ArrayAccess|array  $array
 * @param  string|int|null  $key
 * @param  mixed  $default
 * @return mixed
 */
public static function get($array, $key, $default = null)
{
    if (! static::accessible($array)) {
        return value($default);
    }

    if (is_null($key)) {
        return $array;
    }

    if (static::exists($array, $key)) {
        return $array[$key];
    }

    if (strpos($key, '.') === false) {
        return $array[$key] ?? value($default);
    }

    foreach (explode('.', $key) as $segment) {
        if (static::accessible($array) && static::exists($array, $segment)) {
            $array = $array[$segment];
        } else {
            return value($default);
        }
    }

    return $array;
}

説明の通り、「ドット」表記を使用して配列からアイテムを取得するメソッドのようです。第一引数に ArrayAccess か配列を、第二引数にキー、第三引数にデフォルト値を受け取ります。受け取った第一引数が ArrayAccess のインスタンスでなく且つ配列でもないものならデフォルト値を戻す、キーがnullなら第一引数をそのまま戻す。第一引数が ArrayAccess で且つ、その中に第二引数がオフセットとして存在する場合、若しくは第一引数が配列であり且つ第二引数がキーとして存在する場合は第一引数の第二引数オフセット、もしくは第一引数配列のキーが第二引数の値を戻す。第二引数に「.」が含まれていない場合は、第一引数の第二引数オフセットもしくは第二引数配列のキーが第二引数の値があればそれを、なければ第三引数デフォルト値を戻す。冗長な部分があるきがしますが、気にせず次に行きましょう。

ここからがメインです。キーを「.」で分割し配列にしforeach で回します。 「.」で区切られた文字列の値で第一引数をネストして、オフセットであるか、配列のキーに存在するかを確認していきます。正当性の確認が取れた場合第一引数から第二引数の「.」記法で指定されたオブジェクト若しくは配列を戻し、正当性が取れない場合はデフォルト値を戻します。 まぁ、平たく言えばドットシンタックスを使ってオブジェクトか配列を階層構造を追って取得する感じでしょうか。 本題に戻りましょう。

$this->items

Arr::get() の挙動は理解できましたがこれが引数として受け取る $array がまだ追えていません。configの定数なのではないかと予想はできますが、一応追ってみましょう。Arr::get を呼び出しているRepository::get() には、Arr::get($this->items, $key, $default) とあります。$this->itemsRepository クラスのコンストラクタでセットされるもののようです。このインスタンスはDatabaseManagerインスタンスの$this->app['config']で、$this->appDatabaseManager::__construct$app がセットされています。つまり Laravelアプリケーション自体で初期化したものであるだろうと予測できます。では、Larabelアプリケーションの初期設定の流れを追ってみましょう。

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\Kernelcomposer/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::$repositorynull でない場合はそれを返し、 null だった場合は static::$repository を構築する流れのようです。 static::$repository 自体は Dotenv/Repository/RepositoryInterface インターフェイスを実装したインスタンスのようです。処理の中で以下のようなクラスが書かれています。

これらは、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 なので、$valueAPP_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() が呼び出されます。このメソッドは basePathbootstrap ディレクトリを追加して引数の文字列を連結したものを返します。結果、PROJECT_ROOT/bootstrap/cache/config.php という文字列が返されます。APP_CONFIG_CACHE が存在していた場合は、Str::startsWith($env, '/') が判定され文字列加工をします。startsWith は Java などで使われる関数でPHP関数にないものを独自に定義したもののようです。文字列が引数で指定された文字列で始まるかを判定して truefalse を返します。LoadConfiguration::bootstrap() にそのパスが返され、file_exists でそのファイルが存在するか判定され、存在した場合はそのキャッシュファイルを読み込み、読み込みフラグ $loadedFromCachetrue がセットされます。 そして、存在していた場合は読み込んだキャッシュを、存在していない場合は空の配列をアプリケーションコンテナに 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 が代入されます。次に foreachFinder クラスで色々したものを回しています。 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->modeIterator\FileTypeFilterIterator::ONLY_FILES を代入しています。 ONLY_FILES1 が定義されています。ファイルだけ一覧にするモード定数なのでしょう。次に 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 の正体が明確になりました。本筋に戻りましょう。

DatabaseManagerクラス : データベース接続

かなり回り道しましたが、DatabaseManager::getDefaultConnection() に返される $this->app['config']['database.default']PROJECT_ROOT/config/database.php に記述されている配列の 「default」キーの値ということがわかりました。デフォルトでは 「mysql」 と設定されています。

DatabaseManager クラスの処理の続きを見てみましょう。

Illuminate/Database/DatabaseManager::connection() | 関連メソッド

/**
 * Get a database connection instance.
 *
 * @param  string|null  $name
 * @return \Illuminate\Database\Connection
 */
public function connection($name = null)
{
    [$database, $type] = $this->parseConnectionName($name);

    $name = $name ?: $database;

    // If we haven't created this connection, we'll create it based on the config
    // provided in the application. Once we've created the connections we will
    // set the "fetch mode" for PDO which determines the query return types.
    if (! isset($this->connections[$name])) {
        $this->connections[$name] = $this->configure(
            $this->makeConnection($database), $type
        );
    }

    return $this->connections[$name];
}
/**
 * Parse the connection into an array of the name and read / write type.
 *
 * @param  string  $name
 * @return array
 */
protected function parseConnectionName($name)
{
    $name = $name ?: $this->getDefaultConnection();

    return Str::endsWith($name, ['::read', '::write'])
                        ? explode('::', $name, 2) : [$name, null];
}

parseConnectionName() の戻り値は以下のようになっています。

return Str::endsWith($name, ['::read', '::write']) ? explode('::', $name, 2) : [$name, null];

Str::endsWith() は Java などの他の言語にある文字列加工関数を PHP で使えるように定義したもので、調べたい文字列が特定の文字列で終わるか検証し、ブーリアンで返すものです。。検証用の文字列は string|string[] を受け入れることができるので、複数の条件を指定することができます。今回は、「::read」か「::write」で終わるかどうか調べています。この結果が true だった場合、「::」 で explode し、配列の0番目をデータベース名、1番目を接続タイプとして返します。false だった場合、$name = $name ?: $this->getDefaultConnection() の結果をそのままデータベース名として、接続タイプを null として返します。データベースを読み込みと書き込みで分ける設計の場合に使うためのものなのでしょう。
DatabaseManager::connection() に戻りましょう。

connection() メソッドでデータベース名を受け取った場合、それを、指定がない場合は先程の処理で割り出したデフォルトデータベース名を接続データベース名として確定します。
$this->connections[] 配列に確定した接続データベース名をキーにした値が存在するか調べます。DatabaseManager インスタンスは connections[] で接続データベースを管理しているようです。もし存在していない場合はconnections[] に接続データベース名をキーとして接続を追加します。

configure() メソッドを makeConnection() メソッドの結果と接続タイプを引数と渡してコールしています。makeConnection() メソッドから見てみましょう。

Illuminate/Database/DatabaseManager::makeConnection() | 関連クラス

/**
 * Make the database connection instance.
 *
 * @param  string  $name
 * @return \Illuminate\Database\Connection
 */
protected function makeConnection($name)
{
    $config = $this->configuration($name);

    // First we will check by the connection name to see if an extension has been
    // registered specifically for that connection. If it has we will call the
    // Closure and pass it the config allowing it to resolve the connection.
    if (isset($this->extensions[$name])) {
        return call_user_func($this->extensions[$name], $config, $name);
    }

    // Next we will check to see if an extension has been registered for a driver
    // and will call the Closure if so, which allows us to have a more generic
    // resolver for the drivers themselves which applies to all connections.
    if (isset($this->extensions[$driver = $config['driver']])) {
        return call_user_func($this->extensions[$driver], $config, $name);
    }

    return $this->factory->make($config, $name);
}
/**
 * Get the configuration for a connection.
 *
 * @param  string  $name
 * @return array
 *
 * @throws \InvalidArgumentException
 */
protected function configuration($name)
{
    $name = $name ?: $this->getDefaultConnection();

    // To get the database connection configuration, we will just pull each of the
    // connection configurations and get the configurations for the given name.
    // If the configuration doesn't exist, we'll throw an exception and bail.
    $connections = $this->app['config']['database.connections'];

    if (is_null($config = Arr::get($connections, $name))) {
        throw new InvalidArgumentException("Database connection [{$name}] not configured.");
    }

    return (new ConfigurationUrlParser)
                ->parseConfiguration($config);
}

makeConnection() メソッドは引数として接続名を受け取り、それをそのまま引数として configuration() をコールします。 configuration() は接続名を引数として受け取り、もし、接続名がなければ先程読んだ処理と同じくデフォルトの接続名を代用します。
config リポジトリから 「database.connections」を取り出します。通常は PROJECT_ROOT/config/database.php に記述されている配列の「connections」 をキーとした中身です。引数としてして指定された接続名がdatabase.connections に存在しない場合は「Database connection [{$name}] not configured.」というメッセージを添えて InvalidArgumentException をスローします。
取り出した接続情報を引数として ConfigurationUrlParser::parseConfiguration() メソッドをコールします。
ConfigurationUrlParser クラスは Illuminate/Support/ConfigurationUrlParser です。このクラスはデータベースの種類に応じて接続設定用の配列を生成してくれるもののようです。対応データベースは以下です。

これで生成された配列を makeConnection() に戻します。
その後、extensions[] 配列に接続名があるか、接続用ドライバーがあるか調べコールバック関数を叩いています。おそらくこれは上に上げたデータベースの種類以外のものを登録して使えるようにするためのものでしょう。
そして

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

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

Illuminate/Support/Traits/EnumeratesValues::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;
}

Illuminate/Support/Collection::__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->itemsgetArrayableItems メソッドを通して格納しています。この Collection クラスは Enumerable インターフェイスを継承しています。 Illuminate/Support/Enumerable クラスは複数のインターフェイスを実装しています。

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

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

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

Illuminate/Support/Traits/EnumeratesValues::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) ', '!==']);
        }

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

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

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

$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 に代入するということのようです。
次に行きましょう。

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

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

Illumination/Support/Collection::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));
}

 

引数が1つの場合、$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 の設定値を追加しています。

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

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

Illuminate/Foundation/ProviderRepository::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) 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, ' []], $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.phpproviders に記述されているサービスを読み込む流れがわかりました。

PROJECT_ROOT/config/app.phpproviders には、 Illuminate\Database\DatabaseServiceProvider が記述されています。このプロバイダーが初期化時に読み込まれ、register() メソッドがコールされます。

Illuminate\Database\DatabaseServiceProvider::register() | 関連メソッド

/**
 * Register the service provider.
 *
 * @return void
 */
public function register()
{
    Model::clearBootedModels();

    $this->registerConnectionServices();

    $this->registerEloquentFactory();

    $this->registerQueueableEntityResolver();
}

/**
 * Register the primary database bindings.
 *
 * @return void
 */
protected function registerConnectionServices()
{
    // The connection factory is used to create the actual connection instances on
    // the database. We will inject the factory into the manager so that it may
    // make the connections while they are actually needed and not of before.
    $this->app->singleton('db.factory', function ($app) {
        return new ConnectionFactory($app);
    });

    // The database manager is used to resolve various connections, since multiple
    // connections might be managed. It also implements the connection resolver
    // interface which may be used by other components requiring connections.
    $this->app->singleton('db', function ($app) {
        return new DatabaseManager($app, $app['db.factory']);
    });

    $this->app->bind('db.connection', function ($app) {
        return $app['db']->connection();
    });
}


register() メソッドから、 registerConnectionServices() メソッドがコールされ、そこで以下がシングルトン結合びバインドされます。

まず、 db.factory として Illumination/Database/Connectors/ConnectionFactory をシングルトン結合します。次に db として、Illumination/Database/DatabaseManager を先程結合した db.factory を引数として渡して生成しシングルトン結合します。最後に db.connection として先細生成した$app['db']connection() メソッドを使い生成した接続をバインドします。
これでデータベース接続を確立する時の流れがわかりました。

長くなりましたので 【フレームワークを読む】Laravelのマイグレーションファイルは何をしているのか? :その2 に続きます。

 

執筆中