2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!その2「$app['db']って何者?」

Last updated at Posted at 2020-04-13

INDEX

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

$app[‘db’]について

というわけで、Schema::getFacadeAccessor()メソッドから返される戻り値static::$app['db']->connection()->getSchemaBuilder()を追いかけて見ましょう!

おそらく、順序が逆で先に見るべきものだったのだろう存在があります。
マイグレーションファイルを利用する時、artisanmigrateを利用するそうです。
以下のようなコマンドを叩きます。

artisan::migrate
$ php artisan migrate

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のオートロードを読み込み、$app PROJECT_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クラスはIlluminate\Container\Containerで定義されています。このクラスはArrayAccessをインターフェースとして実装しています。

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()メソッドですね!
Containerクラスを読んでみましょう。

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);
        }
    }
}
Illuminate\Foundation\Application::getAlias()
    /**
     * Get the alias for an abstract if available.
     *
     * @param  string  $abstract
     * @return string
     */
    public function getAlias($abstract)
    {
        if (! isset($this->aliases[$abstract])) {
            return $abstract;
        }

        return $this->getAlias($this->aliases[$abstract]);
    }

随分あちこち回りましたが、つまりSchema::getFacadeAccessorstatic::$app['db']Illuminate\Database\ConnectionResolverInterfaceを実装したIlluminate\Database\DatabaseManagerのインスタンスのことのようです。
生成される流れがいまいちしっくり来ませんが、おそらくもっと読んで行けばわかりそうですので先に行きましょう。

探しているのはstatic::$app['db']->connection()です。Illuminate\Database\DatabaseManagerにあるはずです。見てみましょう。

DatabaseManagerクラス

**$app['db']**の正体はDatabaseManagerインスタンスっぽいです。ぽいというのは正確な生成手続きがまだよくわからないので推測の段階です。まぁ、気にせずどんどん進みましょう。
Illuminate\Support\Facades\Schema::getFacadeAccessor()では以下のような記述がありました。

Illuminate\Support\Facades\Schema::getFacadeAccessor()
 protected static function getFacadeAccessor()
 {
    return static::$app['db']->connection()->getSchemaBuilder();
}

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

Illuminate/Database/DatabaseManager:άonnection()|関連メソッド
/**
 * 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']が何者なのか知る必要がありますね!

次回

次回は**app['config']**について追ってみたいと思います。

続く☆

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?