いよいよ builder クラスの build() メソッドです。
なんか同じようなこと何回も言ってる気がしますが、どんどん読んでいきましょう。
Illuminate\Database\Schema\Builder::build()
/**
* Execute the blueprint to build / modify the table.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return void
*/
protected function build(Blueprint $blueprint)
{
$blueprint->build($this->connection, $this->grammar);
}
Illuminate\Database\Schema\Blueprint::build() | 関連メソッド
/**
* Execute the blueprint against the database.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return void
*/
public function build(Connection $connection, Grammar $grammar)
{
foreach ($this->toSql($connection, $grammar) as $statement) {
$connection->statement($statement);
}
}
/**
* Get the raw SQL statements for the blueprint.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return array
*/
public function toSql(Connection $connection, Grammar $grammar)
{
$this->addImpliedCommands($grammar);
$statements = [];
// Each type of command has a corresponding compiler function on the schema
// grammar which is used to build the necessary SQL statements to build
// the blueprint element, so we'll just call that compilers function.
$this->ensureCommandsAreValid($connection);
foreach ($this->commands as $command) {
$method = 'compile'.ucfirst($command->name);
if (method_exists($grammar, $method) || $grammar::hasMacro($method)) {
if (! is_null($sql = $grammar->$method($this, $command, $connection))) {
$statements = array_merge($statements, (array) $sql);
}
}
}
return $statements;
}
Builder::build() メソッドは先程準備した Blueprint インスタンスを引数に受け取ります。
Blueprint::build() メソッドを MySqlConnection と MySqlGrammar インスタンスを引数に渡しコールします。
Blueprint::build() メソッドは受け取った引数2つを toSql() メソッドに渡し戻り値を foreach で回します。
toSql() メソッドは引数として受け取った MySqlGrammar インスタンスを addImpliedCommands() メソッドに渡します。
Illuminate\Database\Schema\Blueprint:: addImpliedCommands()
/**
* Add the commands that are implied by the blueprint's state.
*
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return void
*/
protected function addImpliedCommands(Grammar $grammar)
{
if (count($this->getAddedColumns()) > 0 && ! $this->creating()) {
array_unshift($this->commands, $this->createCommand('add'));
}
if (count($this->getChangedColumns()) > 0 && ! $this->creating()) {
array_unshift($this->commands, $this->createCommand('change'));
}
$this->addFluentIndexes();
$this->addFluentCommands($grammar);
}
/**
* Get the columns on the blueprint that should be added.
*
* @return \Illuminate\Database\Schema\ColumnDefinition[]
*/
public function getAddedColumns()
{
return array_filter($this->columns, function ($column) {
return ! $column->change;
});
}
Illuminate\Support\Fluent::__get() | 関連メソッド
/**
* Dynamically retrieve the value of an attribute.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Get an attribute from the fluent instance.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
return value($default);
}
addImpliedCommands() メソッドは getAddedColumns() の戻り値の配列の数を検証します。
getAddedColumns() は $this->columns 配列に含まれる ColumnDefinition インスタンスの中で、change のパラメーターがあるものを省いた配列を戻します。
ColumnDefinition クラスは Fluent クラスを継承しています。
Fluent クラスはマジックメソッド __get() が定義してあり、get() メソッドの戻り値を戻します。 get() メソッドは $this->attributes[] に引数をキーとした値があればそれを、なければデフォルト指定された値を。指定がなければ null を戻します。array_filter() はコールバックが true を戻すもののみを配列にしたものを戻しますので、$column->change が null のもののみ配列にしたものが戻ってきます。$this->columns には id migration batch が登録された ColumnDefinition が入っています。そのどれも change をセットはしていませんので、そのまま戻ってきます。
getAddedColumns() の戻り値の配列の数が 0 より大きかった場合、さらに $this->creating() メソッドを検証します。
creating() メソッドを見てみましょう。
Illuminate\Database\Schema\Blueprint:: creating()
/**
* Determine if the blueprint has a create command.
*
* @return bool
*/
protected function creating()
{
return collect($this->commands)->contains(function ($command) {
return $command->name === 'create';
});
}
Illuminate\Support\helpers.php 抜粋
/**
* Create a collection from the given value.
*
* @param mixed $value
* @return \Illuminate\Support\Collection
*/
function collect($value = null)
{
return new Collection($value);
}
collect() ヘルパ関数を使っています。
collect() ヘルパ関数は引数として受け取った値を引数として渡して Collection インスタンスを生成し、それを戻しています。
つまり、 $this->commands を引数として渡して生成した Collection インスタンスの contains() メソッドをクロージャーを引数として渡してコールするという流れになります。
Collection クラスを見てみましょう。
Illuminate\Support\Collection 抜粋
use EnumeratesValues, Macroable;
/**
* The items contained in the collection.
*
* @var array
*/
protected $items = [];
/**
* Create a new collection.
*
* @param mixed $items
* @return void
*/
public function __construct($items = [])
{
$this->items = $this->getArrayableItems($items);
}
/**
* Determine if an item exists in the collection.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function contains($key, $operator = null, $value = null)
{
if (func_num_args() === 1) {
if ($this->useAsCallable($key)) {
$placeholder = new stdClass;
return $this->first($key, $placeholder) !== $placeholder;
}
return in_array($key, $this->items);
}
return $this->contains($this->operatorForWhere(...func_get_args()));
}
/**
* Get the first item from the collection passing the given truth test.
*
* @param callable|null $callback
* @param mixed $default
* @return mixed
*/
public function first(callable $callback = null, $default = null)
{
return Arr::first($this->items, $callback, $default);
}
Illuminate\Support\Traits\EnumeratesValues::getArrayableItems
/**
* 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;
}
/**
* 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);
}
Collection クラスはコンストラクタで受け取った引数を getArrayableItems() メソッドに渡した戻り値を変数 $this->items に代入しています。今回入るのは、「create 」が登録されたFluent インスタンスが入った配列です。
getArrayableItems() はトレイト EnumeratesValues に定義されています。これは前にも見たことがありますね。引数の型に合わせて適切に配列型に直し戻すメソッドでしたね。つまり、Collection::$items は配列型であることが保証されます。
では contains() メソッドを見てみましょう。
受け取った引数が1つのみでそれがコールバック関数だった場合、stdClass を生成し、first() メソッドをコールバックと stdClass を引数として渡してコールし、戻ってきたものが stdClass と等価であるか検証し、等価でなければ true そうでなければ false を戻します。
first() メソッドを見てみましょう。
Arr::first() を登録されたコマンド配列とコールバックを引数として渡してコールしています。
Arr::first() を見てみましょう。
Illuminate\Support\Arr::first()
/**
* Return the first element in an array passing a given truth test.
*
* @param iterable $array
* @param callable|null $callback
* @param mixed $default
* @return mixed
*/
public static function first($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return value($default);
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return value($default);
}
引数として渡されたコールバックが null で、渡された配列が空ならばデフォルトを、空でなければ1番目の配列の中身を戻しています。
コールバック関数が渡されていた場合、配列を foreach で回し、 値とキーを引数にコールバックをコールした戻り値が false でなければその値を返します。
foreach が全て回されコールバックの戻り値が全て false だった場合、デフォルト値を戻します。
今回渡されたコールバックは以下でした。
return collect($this->commands)->contains(function ($command) {
return $command->name === 'create';
});
配列として渡されたものはコマンド配列で中身は ColumnDefinition インスタンスでした。ColumnDefinition インスタンスで name が「create」 のものを返すという処理のようです。今回は name が 「create」 ですので、戻り値は 「create」ですね。ということで、
addImpliedCommands() メソッドのはじめの処理は、$this->columns 配列の中に含まれる ColumnDefinition インスタンスの中で、「change」 パラメーターが無いものが存在し、且つ 「create 」のパラメータが含まれていない場合は $this->commands に $this->createCommand('add') の戻り値を先頭に加えるというものですね。
$this->createCommand('add') は前に読みましたね。
引数にコマンド名と引数配列を受け取り、compact() で「name 」というキーにコマンド名を代入した配列と引数配列を array_marge() したものを引数として Fluent インスタンスを生成して戻すものでした。
addImpliedCommands() メソッドの次の処理を読みましょう。
最初のものと似ていますね。
if (count($this->getChangedColumns()) > 0 && ! $this->creating()) {
array_unshift($this->commands, $this->createCommand('change'));
}
Illuminate\Database\Schema\Blueprint::getChangedColumns()
/**
* Get the columns on the blueprint that should be changed.
*
* @return \Illuminate\Database\Schema\ColumnDefinition[]
*/
public function getChangedColumns()
{
return array_filter($this->columns, function ($column) {
return (bool) $column->change;
});
}
getChangedColumns() メソッドは $this->columns 配列に含まれる ColumnDefinition インスタンスの中で 「change」 パラメーターがあるものが存在し、且つ「create」 パラメーターが含まれていない場合は$this->commands に $this->createCommand('change') の戻り値を先頭に加えるというものです。
addImpliedCommands() の続きです。
$this->addFluentIndexes(); $this->addFluentCommands($grammar);
addFluentIndexes() を見てみましょう。
Illuminate\Database\Schema\Blueprint::addFluentIndexes()
/**
* Add the index commands fluently specified on columns.
*
* @return void
*/
protected function addFluentIndexes()
{
foreach ($this->columns as $column) {
foreach (['primary', 'unique', 'index', 'spatialIndex'] as $index) {
// If the index has been specified on the given column, but is simply equal
// to "true" (boolean), no name has been specified for this index so the
// index method can be called without a name and it will generate one.
if ($column->{$index} === true) {
$this->{$index}($column->name);
$column->{$index} = false;
continue 2;
}
// If the index has been specified on the given column, and it has a string
// value, we'll go ahead and call the index method and pass the name for
// the index since the developer specified the explicit name for this.
elseif (isset($column->{$index})) {
$this->{$index}($column->name, $column->{$index});
$column->{$index} = false;
continue 2;
}
}
}
}
$this->columns を foreach で回し、ColumnDefinition::$attributes に primary unique index spatialIndex が含まれているか確認し、含まれている場合、Blueprint の各インデックス名メソッドでカラム名を登録します。
Illuminate\Database\Schema\Blueprint インデックス登録メソッド
/**
* Specify the primary key(s) for the table.
*
* @param string|array $columns
* @param string|null $name
* @param string|null $algorithm
* @return \Illuminate\Support\Fluent
*/
public function primary($columns, $name = null, $algorithm = null)
{
return $this->indexCommand('primary', $columns, $name, $algorithm);
}
/**
* Specify a unique index for the table.
*
* @param string|array $columns
* @param string|null $name
* @param string|null $algorithm
* @return \Illuminate\Support\Fluent
*/
public function unique($columns, $name = null, $algorithm = null)
{
return $this->indexCommand('unique', $columns, $name, $algorithm);
}
/**
* Specify an index for the table.
*
* @param string|array $columns
* @param string|null $name
* @param string|null $algorithm
* @return \Illuminate\Support\Fluent
*/
public function index($columns, $name = null, $algorithm = null)
{
return $this->indexCommand('index', $columns, $name, $algorithm);
}
/**
* Specify a spatial index for the table.
*
* @param string|array $columns
* @param string|null $name
* @return \Illuminate\Support\Fluent
*/
public function spatialIndex($columns, $name = null)
{
return $this->indexCommand('spatialIndex', $columns, $name);
}
/**
* Add a new index command to the blueprint.
*
* @param string $type
* @param string|array $columns
* @param string $index
* @param string|null $algorithm
* @return \Illuminate\Support\Fluent
*/
protected function indexCommand($type, $columns, $index, $algorithm = null)
{
$columns = (array) $columns;
// If no name was specified for this index, we will create one using a basic
// convention of the table name, followed by the columns, followed by an
// index type, such as primary or index, which makes the index unique.
$index = $index ?: $this->createIndexName($type, $columns);
return $this->addCommand(
$type, compact('index', 'columns', 'algorithm')
);
}
/**
* Create a default index name for the table.
*
* @param string $type
* @param array $columns
* @return string
*/
protected function createIndexName($type, array $columns)
{
$index = strtolower($this->prefix.$this->table.'_'.implode('_', $columns).'_'.$type);
return str_replace(['-', '.'], '_', $index);
}
addCommand() メソッドは $this->commands[] にcreateCommand() メソッドの戻り値を代入しそれを戻すものでした。戻り値は Fluent インスタンです。
続きの addFluentCommands() メソッドを見てみましょう。引数として MySqlGrammar インスタンスを渡しています。
Illuminate\Database\Schema\Blueprint::addFluentCommands()
/**
* Add the fluent commands specified on any columns.
*
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return void
*/
public function addFluentCommands(Grammar $grammar)
{
foreach ($this->columns as $column) {
foreach ($grammar->getFluentCommands() as $commandName) {
$attributeName = lcfirst($commandName);
if (! isset($column->{$attributeName})) {
continue;
}
$value = $column->{$attributeName};
$this->addCommand(
$commandName, compact('value', 'column')
);
}
}
}
Illuminate\Database\Schema\Grammars\Grammar::getFluentCommands()
/**
* The commands to be executed outside of create or alter command.
*
* @var array
*/
protected $fluentCommands = [];
/**
* Get the fluent commands for the grammar.
*
* @return array
*/
public function getFluentCommands()
{
return $this->fluentCommands;
}
Illuminate\Database\Schema\Grammars\PostgresGrammar
/**
* The commands to be executed outside of create or alter command.
*
* @var array
*/
protected $fluentCommands = ['Comment'];
$this->columns を foreach で回し、$grammar->getFluentCommands() で $fluentCommands[] を取得しています。これは、各データベースに合わせた Grammar クラスで設定されているようですが、設定されているのは Postgres のみです。設定されている場合はコマンドに追加します。
toSql() メソッドの続きを見ましょう。
Illuminate\Database\Schema\Blueprint::toSql()
/**
* Get the raw SQL statements for the blueprint.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return array
*/
public function toSql(Connection $connection, Grammar $grammar)
{
$this->addImpliedCommands($grammar);
$statements = [];
// Each type of command has a corresponding compiler function on the schema
// grammar which is used to build the necessary SQL statements to build
// the blueprint element, so we'll just call that compilers function.
$this->ensureCommandsAreValid($connection);
foreach ($this->commands as $command) {
$method = 'compile'.ucfirst($command->name);
if (method_exists($grammar, $method) || $grammar::hasMacro($method)) {
if (! is_null($sql = $grammar->$method($this, $command, $connection))) {
$statements = array_merge($statements, (array) $sql);
}
}
}
return $statements;
}
/**
* Ensure the commands on the blueprint are valid for the connection type.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*
* @throws \BadMethodCallException
*/
protected function ensureCommandsAreValid(Connection $connection)
{
if ($connection instanceof SQLiteConnection) {
if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1) {
throw new BadMethodCallException(
"SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."
);
}
if ($this->commandsNamed(['dropForeign'])->count() > 0) {
throw new BadMethodCallException(
"SQLite doesn't support dropping foreign keys (you would need to re-create the table)."
);
}
}
}
$statements に空の配列を代入します。
ensureCommandsAreValid() メソッドをコールしていますが、このメソッドの中身は SQLite 接続の場合の例外処理のようです。外部キーの削除や複数のテーブルドロップ、カラム名変更が仕様上出来ないための対応のようです。
$this->commands を foreach で回します。中身はコマンド名を登録した Fluent インスタンスです。回したコマンド名からメソッド名を整形し、MySqlGrammar クラスにそのメソッドが実装されている若しくはトレイトの $macros に登録されているか検証します。
存在した場合、MySqlGrammar の指定メソッドを自身とコマンドが登録された Fluent と接続を引数として渡しコールします。
今回は 「’create’」 コマンドが登録されていますので、MySqlGrammar::compileCreate() ですね。
見てみましょう。
Illuminate\Database\Schema\Grammars\MySqlGrammar::compileCreate()
/**
* Compile a create table command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @param \Illuminate\Database\Connection $connection
* @return string
*/
public function compileCreate(Blueprint $blueprint, Fluent $command, Connection $connection)
{
$sql = $this->compileCreateTable(
$blueprint, $command, $connection
);
// Once we have the primary SQL, we can add the encoding option to the SQL for
// the table. Then, we can check if a storage engine has been supplied for
// the table. If so, we will add the engine declaration to the SQL query.
$sql = $this->compileCreateEncoding(
$sql, $connection, $blueprint
);
// Finally, we will append the engine configuration onto this SQL statement as
// the final thing we do before returning this finished SQL. Once this gets
// added the query will be ready to execute against the real connections.
return $this->compileCreateEngine(
$sql, $connection, $blueprint
);
}
Illuminate\Database\Schema\Grammars\MySqlGrammar::compileCreate()
第一引数は Blueprint インスタンスです。
第二引数は Fluent インスタンスでコマンドです。
第三引数は Connection インスタンスで接続です。
戻り値はストリング型で SQL文です。
compileCreateTable() をコールしています。
Illuminate\Database\Schema\Grammars\MySqlGrammar::compileCreateTable()
/**
* Create the main create table clause.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @param \Illuminate\Database\Connection $connection
* @return string
*/
protected function compileCreateTable($blueprint, $command, $connection)
{
return sprintf('%s table %s (%s)',
$blueprint->temporary ? 'create temporary' : 'create',
$this->wrapTable($blueprint),
implode(', ', $this->getColumns($blueprint))
);
}
Illuminate\Database\Schema\Grammars\Grammar::wrapTable()
/**
* Wrap a table in keyword identifiers.
*
* @param mixed $table
* @return string
*/
public function wrapTable($table)
{
return parent::wrapTable(
$table instanceof Blueprint ? $table->getTable() : $table
);
}
Illuminate\Database\Schema\Grammars::wrapTable()
/**
* Wrap a table in keyword identifiers.
*
* @param \Illuminate\Database\Query\Expression|string $table
* @return string
*/
public function wrapTable($table)
{
if (! $this->isExpression($table)) {
return $this->wrap($this->tablePrefix.$table, true);
}
return $this->getValue($table);
}
Illuminate\Database\Schema\Blueprint::getTable()
/**
* Get the table the blueprint describes.
*
* @return string
*/
public function getTable()
{
return $this->table;
}
/**
* Determine if the given value is a raw expression.
*
* @param mixed $value
* @return bool
*/
public function isExpression($value)
{
return $value instanceof Expression;
}
SQL文を生成します。
Blurprint::$temporary が true ならば、「create temporary」 false ならば
「create 」が入ります。一時テーブルの指定が出来るようですね。今回は使いません。
wrapTable() メソッドを引数に Blueprint インスタンスを渡しコールしています。
Illuminate\Database\Schema\Grammars\Grammar::wrapTable() メソッドは受け取った引数が Blueprint インスタンスなら、 Blueprint::getTable() の戻り値つまりテーブル名を、そうでなければ引数として渡されているストリング型のテーブル名を、そのままスーパークラスの wrapTable() メソッドに引数として渡し戻り値を戻しています。
スーパークラスの Illuminate\Database\Schema\Grammars::wrapTable() メソッドは、引数として渡されたテーブルが Expression インスタンスかどうか検証ます。
Expression インスタンスでない場合は、Grammars::wrap() メソッドをテーブルプレフィックスとテーブル名を連結したストリング、及び true を引数として渡してコールした戻り値を戻します。見てみましょう。
Illuminate\Database\Grammar::wrap()
/**
* Wrap a value in keyword identifiers.
*
* @param \Illuminate\Database\Query\Expression|string $value
* @param bool $prefixAlias
* @return string
*/
public function wrap($value, $prefixAlias = false)
{
if ($this->isExpression($value)) {
return $this->getValue($value);
}
// If the value being wrapped has a column alias we will need to separate out
// the pieces so we can wrap each of the segments of the expression on its
// own, and then join these both back together using the "as" connector.
if (stripos($value, ' as ') !== false) {
return $this->wrapAliasedValue($value, $prefixAlias);
}
return $this->wrapSegments(explode('.', $value));
}
/**
* Get the value of a raw expression.
*
* @param \Illuminate\Database\Query\Expression $expression
* @return string
*/
public function getValue($expression)
{
return $expression->getValue();
}
Illuminate\Database\Query\Expression::getValue()
/**
* Get the value of the expression.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
まず重複ですが、第一引数が Expression インスタンスであった場合は、 getValue() メソッドでっ取得した値を戻します。
そうでなかった場合は、第一引数に 「 as 」が含まれるか検証します。
含まれる場合は wrapAliasedValue() メソッドをコールします。
Illuminate\Database\Grammar::wrapAliasedValue()
/**
* Wrap a value that has an alias.
*
* @param string $value
* @param bool $prefixAlias
* @return string
*/
protected function wrapAliasedValue($value, $prefixAlias = false)
{
$segments = preg_split('/\s+as\s+/i', $value);
// If we are wrapping a table we need to prefix the alias with the table prefix
// as well in order to generate proper syntax. If this is a column of course
// no prefix is necessary. The condition will be true when from wrapTable.
if ($prefixAlias) {
$segments[1] = $this->tablePrefix.$segments[1];
}
return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[1]);
}
/**
* Wrap a single string in keyword identifiers.
*
* @param string $value
* @return string
*/
protected function wrapValue($value)
{
if ($value !== '*') {
return '"'.str_replace('"', '""', $value).'"';
}
return $value;
}
受け取ったテーブル名を正規表現で 「 as 」で分割し配列にしたものを $segments に代入します。
プレフィックスが指定されていた場合は、$segments[] 配列の1番目の先頭にプレフィックスを連結します。
連結したものに「 as 」と wrapValue() メソッドを通した $segments[] 配列の1番目 を wrap() メソッドに渡した引数を戻します。と書いてあるのですが、ぱっと見た感じ無限ループしそうなのですが、動くんでしょうか。僕の読みが浅いのかもしれません。
今回は 「 as 」は含まれないのでここは通りません。
wrapSegments() こちらを読みましょう。
Illuminate\Database\Grammar::wrapSegments()
/**
* Wrap the given value segments.
*
* @param array $segments
* @return string
*/
protected function wrapSegments($segments)
{
return collect($segments)->map(function ($segment, $key) use ($segments) {
return $key == 0 && count($segments) > 1
? $this->wrapTable($segment)
: $this->wrapValue($segment);
})->implode('.');
}
テーブル名を 「. 」で explode() したものを引数として受け取ります。
collect() ヘルパ関数に受け取った引数を渡してます。受け取った引数を $items 変数に代入した Collection インスタンスを戻すものでしたね。 Collection::map() メソッドをコールしています。
Illuminate\Support\Collection::map()
/**
* Run a map over each of the items.
*
* @param callable $callback
* @return static
*/
public function map(callable $callback)
{
$keys = array_keys($this->items);
$items = array_map($callback, $this->items, $keys);
return new static(array_combine($keys, $items));
}
$items 配列のキーを取得し配列にしたものを $keys に代入します。
引数として渡されたコールバックに $items と $keys を渡し結果を $items に代入します。
コールバックの内容は、キーが 0 つまり0番目の配列で且つ 引数として渡された $items 中身の値が配列で1より大きければ wrapTable() メソッドに $items の 0 番目を渡してコールした戻り値を、そうでなければ 引数として渡された $items の中身の値を引数としてwrapValue() に渡しコールした戻り値を戻します。
返ってきた値を array_combine() したものを引数として生成した Collection インスタンスを戻します。
map() の戻り値 Collection インスタンスの implode() メソッドを引数に「.」を渡してコールします。
Illuminate\Support\Collection::implode()
/**
* Concatenate values of a given key as a string.
*
* @param string $value
* @param string|null $glue
* @return string
*/
public function implode($value, $glue = null)
{
$first = $this->first();
if (is_array($first) || is_object($first)) {
return implode($glue, $this->pluck($value)->all());
}
return implode($value, $this->items);
}
/**
* Get the values of a given key.
*
* @param string|array $value
* @param string|null $key
* @return static
*/
public function pluck($value, $key = null)
{
return new static(Arr::pluck($this->items, $value, $key));
}
first() メソッドをコールしています。$this->items の最初の値を取得するものでしたね。
取得した値が配列、若しくはオブジェクトの場合、引数として渡された「.」 を引数として渡して pluck() メソッドをコールし、戻り値の all() メソッドの戻り値を 第二引数で連結した文字列を戻します。
そうでなければ、$this->items 配列を 「. 」で連結した文字列を戻します。
今回は、「maigrations」 というストリング型の文字列ですので、ここは通らず、wrapValue() に行きます。
Illuminate\Database\Grammar::wrapValue()
/**
* Wrap a single string in keyword identifiers.
*
* @param string $value
* @return string
*/
protected function wrapValue($value)
{
if ($value !== '*') {
return '"'.str_replace('"', '""', $value).'"';
}
return $value;
}
引数として受け取った文字列が 「*」 でなかった場合は整形して戻してますね。
今回の場合「"migration"」 になります。
ようやく MySqlGrammar::compileCreateTable() が戻す SQL を生成する sprintf() の2つ目の %s までわかりました。今わかっているのは 「create table “migration” (%s)」 ですね。最後の %s を読みましょう。以下となっていました。
implode(', ', $this->getColumns($blueprint))
Illuminate\Database\Schema\Grammars\Grammar::getColumns() | 関連メソッド
/**
* Compile the blueprint's column definitions.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return array
*/
protected function getColumns(Blueprint $blueprint)
{
$columns = [];
foreach ($blueprint->getAddedColumns() as $column) {
// Each of the column types have their own compiler functions which are tasked
// with turning the column definition into its SQL format for this platform
// used by the connection. The column's modifiers are compiled and added.
$sql = $this->wrap($column).' '.$this->getType($column);
$columns[] = $this->addModifiers($sql, $blueprint, $column);
}
return $columns;
}
/**
* Get the columns on the blueprint that should be added.
*
* @return \Illuminate\Database\Schema\ColumnDefinition[]
*/
public function getAddedColumns()
{
return array_filter($this->columns, function ($column) {
return ! $column->change;
});
}
/**
* Get the SQL for the column data type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function getType(Fluent $column)
{
return $this->{'type'.ucfirst($column->type)}($column);
}
Illuminate\Database\Schema\Grammars\Grammar::getColumns()
第一引数は Blueprint インスタンスです。
戻り値は配列でカラムです。
Grammar::getColumns() は引数に Blueprint インスタンスを受け取り、それの getAddedColumns() メソッドの戻り値を foreach で回します。
getAddedColumns() は受け取った Blueprint インスタンスの $columns 配列の内容から change パラメーターの存在するものを削除した配列を戻します。
つまり、 $blueprint->columns で change パラメーターの無いものを foreach します。
foreach した値を wrap() しています。先程読みましたね。ただのストリング型ですので「"」で囲まれた値が返ってきます。それに半角スペースと getType() の戻り値を連結しています。
getType() は 各 $column の type に合わせたメソッドをコールします。
今回追加されるカラムは id migration batch の3つでした。id を生成した時に使われた配列は以下です。
array(4) {
["type"]=> string(7) "integer"
["name"]=> string(2) "id"
["autoIncrement"]=> bool(true)
["unsigned"]=> bool(true)
}
つまりコールされるメソッド名は 「typeInteger()」 ですね。見てみましょう。
TITLE
/**
* Create the column definition for an integer type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function typeInteger(Fluent $column)
{
return 'int';
}
単純にストリング型で 「int」 と返しているだけですね。
$sql に代入される文字列は 「"id" int 」 になりそうです。
その後に $columns[] 配列に $this->addModifiers() をコールした戻り値を代入します。
見てみましょう。
Illuminate\Database\Schema\Grammars\Grammar::addModifiers()
/**
* Add the column modifiers to the definition.
*
* @param string $sql
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function addModifiers($sql, Blueprint $blueprint, Fluent $column)
{
foreach ($this->modifiers as $modifier) {
if (method_exists($this, $method = "modify{$modifier}")) {
$sql .= $this->{$method}($blueprint, $column);
}
}
return $sql;
}
Illuminate\Database\Schema\Grammars\Grammar::addModifiers()
第一引数はストリング型で SQL です。
第二引数は Blueprint インスタンスです。
第三引数は Fluent 型でカラムです。
戻り値はストリング型でSQLです。
$this->modifiers を foreach で回しています。
modifiers は各データベースの種類に合わせて Grammar クラスで定義されています。
以下は MySql のものです。
Illuminate\Database\Schema\Grammars\MySqlGrammar::$modifiers
/**
* The possible column modifiers.
*
* @var array
*/
protected $modifiers = [
'Unsigned', 'Charset', 'Collate', 'VirtualAs', 'StoredAs', 'Nullable',
'Srid', 'Default', 'Increment', 'Comment', 'After', 'First',
];
modifiers 配列に登録された文字列と 「modifier」を連結したメソッドが MySqlGrammar クラスに存在した場合は、そのメソッドを blueprint インスタンスと column インスタンスを引数として渡してコールします。今回 「
id」 に含まれている Unsigned 見てみましょう。
/**
* Get the SQL for an unsigned column modifier.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $column
* @return string|null
*/
protected function modifyUnsigned(Blueprint $blueprint, Fluent $column)
{
if ($column->unsigned) {
return ' unsigned';
}
}
もし、渡された $column に 「unsigned」 が含まれている場合は 「 unsigned」という文字列を戻しています。
各カラムに対して同様の処理を行ったものを代入した配列が戻され、compileCreateTable() で implode() され SQLにが生成されます。おそらく以下のような SQL になるのでしょう。
create table “migration” ( "id" integer unsigned auto_increment primary key, "migration" varchar(255), "batch" integer )
MySqlGrammar::compileCreateTable() の戻り値がわかりました。
MySqlGrammar::compileCreate() の続きです。
次は compileCreateEncoding() がコールされています。見てみましょう。
Illuminate\Database\Schema\Grammars\MySqlGrammar::compileCreateEncoding()
/**
* Append the character set specifications to a command.
*
* @param string $sql
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return string
*/
protected function compileCreateEncoding($sql, Connection $connection, Blueprint $blueprint)
{
// First we will set the character set if one has been set on either the create
// blueprint itself or on the root configuration for the connection that the
// table is being created on. We will add these to the create table query.
if (isset($blueprint->charset)) {
$sql .= ' default character set '.$blueprint->charset;
} elseif (! is_null($charset = $connection->getConfig('charset'))) {
$sql .= ' default character set '.$charset;
}
// Next we will add the collation to the create table statement if one has been
// added to either this create table blueprint or the configuration for this
// connection that the query is targeting. We'll add it to this SQL query.
if (isset($blueprint->collation)) {
$sql .= " collate '{$blueprint->collation}'";
} elseif (! is_null($collation = $connection->getConfig('collation'))) {
$sql .= " collate '{$collation}'";
}
return $sql;
}
Blueprint インスタンスに charset が登録されていればそれを、なければ設定ファイルの文字コードを使い、デフォルト文字コードを設定する SQL を追加します。
Blueprint インスタンスに collation が登録されていればそれを、なければ設定ファイルに照合順序が登録されていればそれを使い照合順序を設定する SQL を追加します。
上記2処理を施した SQL を戻します。
おそらく今回は以下のような SQL になっているでしょう。
create table “migration” ( "id" integer unsigned auto_increment primary key, "migration" varchar(255), "batch" integer ) default character set utf8mb4 collate utf8mb4_unicode_ci
次は compileCreateEngine() です。
見てみましょう。
Illuminate\Database\Schema\Grammars\MySqlGrammar::compileCreateEngine()
/**
* Append the engine specifications to a command.
*
* @param string $sql
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return string
*/
protected function compileCreateEngine($sql, Connection $connection, Blueprint $blueprint)
{
if (isset($blueprint->engine)) {
return $sql.' engine = '.$blueprint->engine;
} elseif (! is_null($engine = $connection->getConfig('engine'))) {
return $sql.' engine = '.$engine;
}
return $sql;
}
$blueprint->engine が登録されていればそれを、登録されてない場合、設定ファイルに登録されていればそれを使いストレージエンジンを設定する SQL文を追加します。今回は指定されておらず、設定ファイルにも記述がないので追加されません。
compileCreate() メソッドが戻す SQL文がわかりました。toSql() メソッドに戻りましょう。
Illuminate\Database\Schema\Blueprint::toSql()
/**
* Get the raw SQL statements for the blueprint.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return array
*/
public function toSql(Connection $connection, Grammar $grammar)
{
$this->addImpliedCommands($grammar);
$statements = [];
// Each type of command has a corresponding compiler function on the schema
// grammar which is used to build the necessary SQL statements to build
// the blueprint element, so we'll just call that compilers function.
$this->ensureCommandsAreValid($connection);
foreach ($this->commands as $command) {
$method = 'compile'.ucfirst($command->name);
if (method_exists($grammar, $method) || $grammar::hasMacro($method)) {
if (! is_null($sql = $grammar->$method($this, $command, $connection))) {
$statements = array_merge($statements, (array) $sql);
}
}
}
return $statements;
}
$this->commands を foreach で回し登録されたコマンドのSQL文を生成し配列に入れたものを戻している処理ということがわかりました。
Blueprint::build() に戻りましょう。
Illuminate\Database\Schema\Blueprint::build() | 関連メソッド
/**
* Execute the blueprint against the database.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return void
*/
public function build(Connection $connection, Grammar $grammar)
{
foreach ($this->toSql($connection, $grammar) as $statement) {
$connection->statement($statement);
}
}
$this->toSql() メソッドは登録されたコマンドから生成されたSQL文が入った配列でした。 foreach でまわして $connection->statement() メソッドをSQL文を引数として渡してコールします。statement() メソッドを見てみましょう。
その15に続きます。