XIONCC

【フレームワークを読む】Laravelのマイグレーションファイルは何をしているのか? :その2:$app[‘db’]について

$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'] を追ってみましょう。

その3に続く