Laravelにはデータベース定義を記述し、作成、ロールバックなどを行えるマイグレーション機能があります。このマイグレーションによるテーブル定義時にはカラムの属性を定義することが出来ます。
データベース:マイグレーション 5.5 Laravel#カラム修飾子
例えば次です。
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable();
});
<a href="https://readouble.com/laravel/5.5/ja/migrations.html#column-modifiers">データベース:マイグレーション 5.5 Laravel#カラム修飾子</a>より引用
これでメソッドnullableの動作によりカラムemailはnullを許すカラムになります。しかしnullable()メソッドの実態を見つけることは少し苦労を要します。
素晴らしいide_helper(皮肉でなく本当にすごく便利)を生成する
barryvdh/laravel-ide-helper: Laravel IDE Helper
を用いても、IDEはこのnullableへの道を見つけることが出来ません。
これはメソッドnullrableが接続先のDBの種類によって動的に生成されているメソッドであるためです。
PHP: オーバーロード – Manual#メソッドのオーバーロード
具体的に何が起きているかがわかる場所は、string()、integer()などの各カラム基本定義メソッドの返り値、ないし返り値が継承しているクラスvendor/laravel/framework/src/Illuminate/Support/Fluent.phpの中、そしてvendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.phpの中にあります。
先のコードは
Schema::table('users', function (Blueprint $table) {
$fluent = $table->string('email');
/** @var Fluent $fluent */
$fluent->nullable();
});
となっており、Fluent内にnullable()は実装されていません。実装されていないメソッドが呼び出されようとする時、__call()が記述されているならば、__call()内部に記述された動作が実行されます。Fluentの__call()は次です。
/**
* Handle dynamic calls to the fluent instance to set attributes.
*
* @param string $method
* @param array $parameters
* @return $this
*/
public function __call($method, $parameters)
{
$this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
return $this;
}
Fluentのインスタンス内に与えられた属性を保持するのみです。この仕組みが理由で次の様なコードを書いてもバグは起きません。
Schema::table('users', function (Blueprint $table) {
$table->string('email')->hogehoge();// 好きな名前でメソッドを呼び出せる
$table->string('name')->nulable();// タイポしても気付かないかもしれないので、善し悪し
});
$table->string(‘email’)->nullable();の様なカラム定義が繰り返される度にBlueprintのインスタンスである$tableはカラム定義のインスタンスであるFluentのインスタンスを新たに持ちます。クロージャ内の処理が終わった後、Schema::table()はBlueprint->build()を実行します。Blueprint->build()の中ではBlueprint->toSql()が実行されます。Blueprint->build()とBlueprint->toSql()の中身は次です。
/**
* 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)
{
// $connectionにデータベースとの接続、$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);// 文法クラス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);// SQLite用処理
foreach ($this->commands as $command) {
// 文法クラス中のcompileから始まる名前のメソッドらの中に、要求された命令に対応するメソッドがあるならばそれを実行してSQL文の元の配列に追加
$method = 'compile'.ucfirst($command->name);
if (method_exists($grammar, $method)) {
if (! is_null($sql = $grammar->$method($this, $command, $connection))) {
$statements = array_merge($statements, (array) $sql);
}
}
}
return $statements;
}
Bladeと同じです。特定のクラス中の特定の名前のメソッドがそのまま対応文法になっています。
【Laravel】Bladeの制御構文の探し方 – 株式会社シーポイントラボ | 浜松のシステム開発会社
まだ終わりません。これでcompileCreateTable->getColumns->addModifiersと動きます。これが次です。
/**
* 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))
);
}
/**
* 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;
}
/**
* 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;
}
このaddModifiersでやっと終わりです。compileHogeHoge同様にmodifyHogeHogeが動きます。modifyHogeHogeがカラムに属性を追加するメソッドになります。modifyHogeHogeは文法クラスに詰まっています。文法クラスはvendor/laravel/framework/src/Illuminate/Database/Schema/Grammars以下に詰まっています。この中から使えるメソッドを抜き出し、ide_helperに追加すればIDEの自動補完が使えるようになります。例えばMySQLならば、次の様になります。
namespace Illuminate\Support{
/**
* @method Fluent after()
* @method Fluent charset()
* @method Fluent collate()
* @method Fluent comment()
* @method Fluent default()
* @method Fluent first()
* @method Fluent increment()
* @method Fluent nullable()
* @method Fluent srid()
* @method Fluent storedAs()
* @method Fluent unsigned()
* @method Fluent virtualAs()
*/
class Fluent {}
}
よく使うuseCurrent()が抜けているので不完全です。少なくともこれで多少はカバーできます。