PROJECT_ROOT/config/app.php の providers には、 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
- db
- db.connection
まず、 db.factory として Illumination/Database/Connectors/ConnectionFactory をシングルトン結合します。次に db として、Illumination/Database/DatabaseManager を先程結合した db.factory を引数として渡して生成しシングルトン結合します。最後に db.connection として先細生成した$app['db'] の connection() メソッドを使い生成した接続をバインドします。
これでいよいよデータベース接続の処理に入ります。
DatabaseManager::makeConnection() メソッドの戻り値の return $this->factory->make($config, $name) の factoryの正体は、 $app['db.factory'] つまり、アプリケーションコンテナを引数として生成した Illumination/Database/Connectors/ConnectionFactory ということですね。では、ConnectionFactory::make() メソッドを考察します。なるべく実行される順番通りに追いかけていきます。
ConnectionFactory::make() の処理の流れ
ConnectionFactory::make() | parseConfig()
/**
* Establish a PDO connection based on the configuration.
*
* @param array $config
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
return $this->createSingleConnection($config);
}
/**
* Parse and prepare the database configuration.
*
* @param array $config
* @param string $name
* @return array
*/
protected function parseConfig(array $config, $name)
{
return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
}
ConnectionFactory::make()
第二引数にストリング型のデータベース名を受け取ります。デフォルトは
null です。戻り値は
Connection インスタンスです。
make() の処理のはじめに、 $config に parseConfig() メソッドの戻り値を入れています。 parseConfig は受け取った接続情報に prefix に空文字を、name に接続名を加えて戻しています。次に接続情報に read が含まれているか判定し、含まれていた場合は createReadWriteConnection() メソッドを、含まれない場合は createSingleConnection() メソッドをコールしたものを戻します。
ConnectionFactory::createReadWriteConnection()
/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));
return $connection->setReadPdo($this->createReadPdo($config));
}
ConnectionFactory::createReadWriteConnection()
$config としてデータベース接続情報を配列型で受け取っています。戻り値は
Connection インスタンスです。
受け取った接続情報を引数に、getWriteConfig() をコールし、戻り値を引数に createSingleConnection() をコールし、戻り値の Connection インスタンスに対して createReadPdo() に接続情報を引数としてコールした戻り値を引数に setReadPdo() メソッドをコールした戻り値を返すというのがこのメソッドの流れです。
ConnectionFactory::getWriteConfig()
/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getWriteConfig(array $config)
{
return $this->mergeReadWriteConfig(
$config, $this->getReadWriteConfig($config, 'write')
);
}
ConnectionFactory::getWriteConfig()
$config としてデータベース接続情報を配列型で受け取っています。戻り値は整形したデータベース接続情報の配列です。
接続情報を受け取り、getReadWriteConfig() メソッドの戻り値の配列を mergeReadWriteConfig() でマージして戻しています。
ConnectionFactory::getReadWriteConfig()
/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
return isset($config[$type][0])
? Arr::random($config[$type])
: $config[$type];
}
ConnectionFactory::getReadWriteConfig()
$config としてデータベース接続情報を配列型で受け取っています。第二引数はストリング型で接続タイプを受け取っています。今回は 「
write」 が入ります。戻り値は整形したデータベース接続情報の配列です。
接続情報配列の接続タイプキー(今回は「write」) の0番目があるか調べています。存在していた場合は Arr::random() をstaticでコールしています。これは、「write」キーが配列である、つまり書き込み用接続が複数ある仕様の時の処理だと思われます。 Arr::random() は PHP の array_rand を強化したstaticメソッドで、指定した配列から指定した数をランダム取得するものです。ソースはとてもかんたんですので興味があれば Illuminate/Support/Arr.php を参照してみると良いと思います。「write」キーの0番目が存在しない場合、つまり接続情報が1つの場合は「write」キーをそのまま返します。
ConnectionFactory::mergeReadWriteConfig()
/**
* Merge a configuration for a read / write connection.
*
* @param array $config
* @param array $merge
* @return array
*/
protected function mergeReadWriteConfig(array $config, array $merge)
{
return Arr::except(array_merge($config, $merge), ['read', 'write']);
}
ConnectionFactory::mergeReadWriteConfig()
第二引数にマージする配列を受け取っています。
戻り値は整形したデータベース接続情報の配列です。
引数として受け取った配列を、array_merge したものを第一引数に、第二引数に ['read', 'write'] として Arr::except() をコールしています。Arr::except() は第一引数で渡された配列を対象に、第二引数として渡された配列で指定されたキーの値を全て取り除いた結果を配列として返します。つまり、['read', 'write'] の情報を削除したものを戻しています。
まだ読んでいる途中ですので、確証はありませんが、読み込みと書き込みの対象データベースが違う場合は、タイプ名をキーにして接続情報を保持していると思われます。接続時にそれを取得し、必要のない接続情報を削除し、接続に必要な情報のみ配列に直に代入するという処理なのだと思われます。書き込み、読み込みの接続は複数設定することが出来、接続ごとにランダムに対象が切り替わるのでしょう。
ConnectionFactory::createSingleConnection()
/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
ConnectionFactory::createSingleConnection()
$config としてデータベース接続情報を配列型で受け取っています。戻り値は
Illuminate\Database\Connection 型です。
createPdoResolver() メソッドでPDO接続用 Connector インスタンスを生成し、createConnection() メソッドで接続を確立した Connection インスタンスを戻します。
ConnectionFactory::createPdoResolver()
/**
* Create a new Closure that resolves to a PDO instance.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolver(array $config)
{
return array_key_exists('host', $config)
? $this->createPdoResolverWithHosts($config)
: $this->createPdoResolverWithoutHosts($config);
}
ConnectionFactory::createPdoResolver()
$config としてデータベース接続情報を配列型で受け取っています。戻り値はクロージャー型です。
受け取った接続情報配列のキーに「host」が存在するか検証し、存在する場合はcreatePdoResolverWithHosts() メソッド、存在しない場合はcreatePdoResolverWithoutHosts メソッドの戻り値のクロージャーを返します。
通常は「host」情報はあると思いますが、SQLiteなどの場合かもしれません。
ConnectionFactory::createPdoResolverWithHosts() | 関連メソッド
/**
* Create a new Closure that resolves to a PDO instance with a specific host or an array of hosts.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolverWithHosts(array $config)
{
return function () use ($config) {
foreach (Arr::shuffle($hosts = $this->parseHosts($config)) as $key => $host) {
$config['host'] = $host;
try {
return $this->createConnector($config)->connect($config);
} catch (PDOException $e) {
continue;
}
}
throw $e;
};
}
/**
* Parse the hosts configuration item into an array.
*
* @param array $config
* @return array
*
* @throws \InvalidArgumentException
*/
protected function parseHosts(array $config)
{
$hosts = Arr::wrap($config['host']);
if (empty($hosts)) {
throw new InvalidArgumentException('Database hosts array is empty.');
}
return $hosts;
}
ConnectionFactory::createPdoResolverWithHosts()
$config としてデータベース接続情報を配列型で受け取っています。戻り値はクロージャー型です。
受け取った配列情報を parseHosts() を通して Arr::wrap() で配列型にキャストしたものを Arr::shuffle() で配列をシャッフルしたものを foreach で回し、接続情報の 「host」 に代入し、createConnector()->connect() で接続の確立を TRY したものを戻すというクロージャーを戻します。接続が確立できなかった場合は PDOEception をスローします。foreach で回していますが、接続が成功したら return 、失敗したら例外をスローするので1回しか回らないように見えます。Arr クラスの静的メソッドはソースはとてもかんたんですので興味があれば Illuminate/Support/Arr.php を参照してみると良いと思います。
これもホスト情報が配列で複数あった場合の処理と推測できます。
ConnectionFactory::createPdoResolverWithoutHosts()
/**
* Create a new Closure that resolves to a PDO instance where there is no configured host.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolverWithoutHosts(array $config)
{
return function () use ($config) {
return $this->createConnector($config)->connect($config);
};
}
ConnectionFactory::createPdoResolverWithoutHosts()
$config としてデータベース接続情報を配列型で受け取っています。戻り値はクロージャーです。
接続情報に 「host」が設定されていなかった場合、受け取った接続情報を引数に createConnector()メソッドをコールし、戻ってきた Connecter インスタンスに接続情報を引数に connect() メソッドをコールし戻ってきた PDO インスタンスを返すクロージャーを戻します。
ConnectionFactory::createConnector()
/**
* Create a connector instance based on the configuration.
*
* @param array $config
* @return \Illuminate\Database\Connectors\ConnectorInterface
*
* @throws \InvalidArgumentException
*/
public function createConnector(array $config)
{
if (! isset($config['driver'])) {
throw new InvalidArgumentException('A driver must be specified.');
}
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
return $this->container->make($key);
}
switch ($config['driver']) {
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
}
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]");
}
ConnectionFactory::createConnector()
$config としてデータベース接続情報を配列型で受け取っています。戻り値は
ConnectorInterface インターフェースを実装したインスタンスです。
受け取った接続情報配列にキーが「driver」の情報が存在しなければ「A driver must be specified.」というメッセージを添えて例外 InvalidArgumentException をスローします。
アプリケーションコンテナに接続ドライバがバインドされている場合はそれを返します。
接続ドライバの設定に合わせて適切なデータベース用のドライバを戻します。デフォルトではMySqlConnector です。
MySqlConnector::connect()
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
// We need to grab the PDO options that should be used while making the brand
// new connection instance. The PDO options control various aspects of the
// connection's behavior, and some might be specified by the developers.
$connection = $this->createConnection($dsn, $config, $options);
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);
$this->setModes($connection, $config);
return $connection;
}
MySqlConnector::connect()
$config としてデータベース接続情報を配列型で受け取っています。戻り値は
PDO インスタンスです。
各データベース用のドライバが用意されています。デフォルトのMySQL用のものを見てみましょう。範囲が広くなりすぎるので、詳細は別の機会に追いたいと思いますので、ざっと見てみます。
DSNとオプションをセットし接続情報と合わせて、 Connection::createConnection() で PDO インスタンスを生成します。
設定情報にデータベース名があればそれを使うように設定します。
エンコードとタイムゾーンとモードを設定してて整えたインスタンスを戻します。
Connector::createConnection()
/**
* Create a new PDO connection.
*
* @param string $dsn
* @param array $config
* @param array $options
* @return \PDO
*
* @throws \Exception
*/
public function createConnection($dsn, array $config, array $options)
{
[$username, $password] = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
Connector::createConnection()
第二引数は配列型で接続情報です。
第三引数は配列型でオプション情報です。
戻り値は
PDO インスタンスです。
接続情報配列からユーザーネームとパスワードを取り出します。存在しなければ null です。
createPdoConnection() メソッドで接続を TRY し、成功の場合は戻り値を返します。
失敗した場合、コネクションロストの可能性を考慮し再度接続を試みます。
Connector::createPdoConnection()
/**
* Create a new PDO connection instance.
*
* @param string $dsn
* @param string $username
* @param string $password
* @param array $options
* @return \PDO
*/
protected function createPdoConnection($dsn, $username, $password, $options)
{
if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
return new PDOConnection($dsn, $username, $password, $options);
}
return new PDO($dsn, $username, $password, $options);
}
/**
* Determine if the connection is persistent.
*
* @param array $options
* @return bool
*/
protected function isPersistentConnection($options)
{
return isset($options[PDO::ATTR_PERSISTENT]) &&
$options[PDO::ATTR_PERSISTENT];
}
Connector::createPdoConnection()
第二引数はストリング型でユーザーネーム情報です。
第三引数はストリング型でパスワード情報です。
第四引数は配列型で接続情報です。
戻り値は
PDO インスタンスです。
PDOConnection クラスが定義済みで、PDO::ATTR_PERSISTENT オプションが true であった場合、PDOConnection インスタンスを、そうでない場合は PDO インスタンスを生成して返します。PDO::ATTR_PERSISTENT はデータベースへの常時接続のパラメーターで、アクセス数が多いサービスなどで毎回接続を確立するよりも常時接続していた方がパフォーマンスを確保できるケースで利用するためのものです。
この、PDOConnectionは Doctrine\DBAL\Driver\PDOConnection なのですが、通常インストール時には composer がインストールしていません。少し調べただけなので確実なことは言えませんが、GitHubのページも 404 Not found. となっています。
同じメソッド名で混同しそうですが、createSingleConnection() でPDOインスタンスを生成した後の処理に続きます。
ConnectionFactory::createConnection()
/**
* Create a new connection instance.
*
* @param string $driver
* @param \PDO|\Closure $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
}
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}]");
}
ConnectionFactory::createConnection()
第二引数は
PDO インスタンスもしくはクロージャーです。第三引数はストリング型でデータベース名です。
第四引数はストリング型でプレフィックスです。
第五引数は
$config としてデータベース接続情報を配列型で受け取っています。戻り値は
Connection インスタンスです。
createSingleConnection() の処理の続きです。
createSingleConnection() には以下のように記述されていました。
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
Connection::getResolver() メソッドの内容は以下です。
Connection::getResolver()
public static function getResolver($driver)
{
return static::$resolvers[$driver] ?? null;
}
受け取ったドライバー名が static変数 Connection::$resolvers[] に存在する場合はそれを、存在しない場合は null を返します。ドライバーを拡張した時に使うのではないかと推測されます。存在しない場合はドライバー名に従って各データベース用の
Connection クラスを生成し戻します。指定したドライバーがなければ「Unsupported driver [ドライバー名]」 というメッセージを添えて 例外
InvalidArgumentException をスローします。デフォルトでの対応ドライバーは、「mysql」「pgsql」「sqlite」「sqlsrv」です。
Laravelのデフォルト設定では生成されるのは
MySqlConnection クラスです。このクラスにはコンストラクタがありません。 Connection クラスを継承しているのでそちらを読んでみましょう。
Connection::__construct()
/**
* Create a new database connection instance.
*
* @param \PDO|\Closure $pdo
* @param string $database
* @param string $tablePrefix
* @param array $config
* @return void
*/
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
{
$this->pdo = $pdo;
// First we will setup the default properties. We keep track of the DB
// name we are connected to since it is needed when some reflective
// type commands are run such as checking whether a table exists.
$this->database = $database;
$this->tablePrefix = $tablePrefix;
$this->config = $config;
// We need to initialize a query grammar and the query post processors
// which are both very important parts of the database abstractions
// so we initialize these to their default values while starting.
$this->useDefaultQueryGrammar();
$this->useDefaultPostProcessor();
}
Connection::__construct()
PDO インスタンス、若しくは PDO インスタンスを返すクロージャーです。第二引数はストリング型でデータベース名です。
第三引数はストリング型でプレフィックスです。
第四引数は配列型で接続情報です。
戻り値はありません。
受け取った引数を変数にセットし、 useDefaultQueryGrammar() メソッドと useDefaultPostProcessor() メソッドをコールします。
Connection::useDefaultQueryGrammar() | 関連メソッド
/**
* Set the query grammar to the default implementation.
*
* @return void
*/
public function useDefaultQueryGrammar()
{
$this->queryGrammar = $this->getDefaultQueryGrammar();
}
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\Grammar
*/
protected function getDefaultQueryGrammar()
{
return new QueryGrammar;
}
Connection::useDefaultQueryGrammar()
useDefaultQueryGrammar() メソッドは $this->queryGrammar に getDefaultQueryGrammar() メソッドの戻り値を代入します。
getDefaultQueryGrammar() メソッドは QueryGrammar インスタンスを生成し戻します。
QueryGrammar の実体は Illuminate\Database\Query\Grammars\Grammar です。
Illuminate\Database\Grammar 抽象クラスを継承しています。これはおそらくSQLを生成するための各種メソッドをまとめたものと推測できます。
Connection::useDefaultPostProcessor() | 関連メソッド
/**
* Set the query post processor to the default implementation.
*
* @return void
*/
public function useDefaultPostProcessor()
{
$this->postProcessor = $this->getDefaultPostProcessor();
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\Processor
*/
protected function getDefaultPostProcessor()
{
return new Processor;
}
Connection::useDefaultPostProcessor()
useDefaultPostProcessor() メソッドは $this->postProcessor に getDefaultPostProcessor() メソッドの戻り値を代入します。
getDefaultPostProcessor() メソッドは Processor インスタンスを生成し戻します。
Processor の実体は Illuminate\Database\Query\Processors\Processor です。おそらくSQLを処理するものと推測できます。あとで出てくると思いますので、その時に読みましょう。
ConnectionFactory::createConnection() メソッドは、接続の確立後に設定するパラメーターやクエリを実行するための準備などをする役割のようです。以上でConnectionFactory::createSingleConnection() メソッドの処理が読み終わりました。ConnectionFactory::createReadWriteConnection() の最後の処理を読んでみましょう。以下のように記述されていました。
return $connection->setReadPdo($this->createReadPdo($config));
createReadPdo() から見てみましょう。
ConnectionFactory::createReadPdo() | 関連メソッド
/**
* Create a new PDO instance for reading.
*
* @param array $config
* @return \Closure
*/
protected function createReadPdo(array $config)
{
return $this->createPdoResolver($this->getReadConfig($config));
}
/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getReadConfig(array $config)
{
return $this->mergeReadWriteConfig(
$config, $this->getReadWriteConfig($config, 'read')
);
}
ConnectionFactory::createReadPdo()
$config としてデータベース接続情報を配列型で受け取っています。戻り値はクロージャーです。
createPdoResolver() メソッドは先程追ったのを覚えていると思います。接続情報にホスト情報が含まれているか調べ、PDOインスタンスを戻すクロージャーを返します。ConnectionFactory::createReadWriteConnection() メソッドはこの処理の前に書き込み接続の設定を使い接続を確立しています。
getReadConfig() メソッドを見てみましょう。見覚えのある記述です。getWriteConfig() メソッドの第二引数を「read」に変えただけのものですね。読込用の接続情報以外を削除し配列を整える処理をしています。
読込接続用の設定で接続を確立するクロージャーを作り返す処理ということです。では、最後にsetReadPdo() を見てみましょう。
Connection::setReadPdo()
/**
* Set the PDO connection used for reading.
*
* @param \PDO|\Closure|null $pdo
* @return $this
*/
public function setReadPdo($pdo)
{
$this->readPdo = $pdo;
return $this;
}
Connection::setReadPdo()
null です。戻り値は
$this です。
受け取った引数を readPdo に代入しています。
このメソッドをコールしたのはすでに書込接続が確立された Connection インスタンスで、接続は pdo に代入されています。書込時は pdo 読み込み時は readPdo と接続を切り替える仕様だろうと推測できます。
以上で、ConnectionFactory::make() の処理の流れを読み終わりました。
データベースに接続する仕組みがよくわかったと思います。
続きは、です。