はじめに
こんにちは!!
ミャンマー人エンジニアのピェッピョーアウンです。
私はミャンマーの大学を卒業して、新卒で日本の企業に就職し、日本での社会人として、5年目が終わり、6年目を向かいました。
エンジニアとして、LaravelとCodeIgniterといったPHP言語を中心にしたWeb開発が多かったのですがこれまでの日々の経験や勉強になったことなど投稿していこうと思っています。
今回はLaravel モデルでのwhere+カラム名メソッドについて、勉強になったことをシェアさせていただければと思います。
where+カラム名メソッドとは
laravelのモデルでwhere+データベースのカラム名のメソッドを使ったことありませんか?
例えば、User
モデルの場合、データベースのusers
テーブルにはname
とemail
カラムがあるとしたら、whereName
、whereEmail
のようなメソッドが定義されてなくても使えるようになっています。
以下、PHPDocで作成された、Userモデルを見てみましょう。
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
/**
* App\User
*
* @property string $name
* @property string $password
* @property string $email
* @method static \Illuminate\Database\Query\Builder|\App\User whereName($value)
* @method static \Illuminate\Database\Query\Builder|\App\User wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\App\User whereEmail($value)
* @mixin \Eloquent
*/
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
ControllerとかServiceとかからはこのような使い方をしています。
$user = User::whereEmail('example@gmail.com');
ではなぜ、where+データベースカラム名の関数が使えるようになってるのでしょうか?
その前にPHPのマジックメソッド__call()
と__callStatic()
を理解していきましょう!
PHPの__call()
と__callStatic()
マジックメソッドとは
マジックメソッドは、 ある動作がオブジェクトに対して行われた場合に、 PHP のデフォルトの動作を上書きする特別なメソッドです。
PHPではたくさんのマジックメソッドがあります。
その中、__call()
は、 アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動します。__callStatic()
マジックメソッドはアクセス不能メソッドをstaticメソッドとして呼び出した場合に起動します。
例えば
class MethodTest
{
public function __call($name, $arguments)
{
echo "Calling object method '$name' ". implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments)
{
echo "Calling static method '$name' ". implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest();
$obj->runTest('in object context');
MethodTest::runTest('in static context');
を実行すると
Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
が出力されて、それぞれが呼び出されます。
Laravel Eloquent の__call()
と__callStatic()
マジックメソッド
LaravelのIlluminate\Database\Eloquent\Model
クラスに下記のようにマジックメソッドが定義されています。
/**
* Handle dynamic method calls into the model.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
/**
* Handle dynamic static method calls into the method.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
なので、User::whereEmail('example@gmail.com')
みたいにstaticメソッドとして呼び出したときに__callStatic()
が起動され、それからオブジェクトコンテキストとして、また__call()
が起動されます。
そして、Illuminate\Support\Traits\ForwardsCalls
traitのforwardCallTo()
メソッドにより、Illuminate\Database\Eloquent\Builder
クラスのインスタンスのメソッドとして、メソッドの呼び出しが転送されます。
Illuminate\Support\Traits\ForwardsCalls
traitのforwardCallTo()
メソッドは以下のように定義されていて、$object->{$method}(...$parameters);
で転送されます。
/**
* Forward a method call to the given object.
*
* @param mixed $object
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
protected function forwardCallTo($object, $method, $parameters)
{
try {
return $object->{$method}(...$parameters);
} catch (Error|BadMethodCallException $e) {
$pattern = '~^Call to undefined method (?P<class>[^:]+)::(?P<method>[^\(]+)\(\)$~';
if (! preg_match($pattern, $e->getMessage(), $matches)) {
throw $e;
}
if ($matches['class'] != get_class($object) ||
$matches['method'] != $method) {
throw $e;
}
static::throwBadMethodCallException($method);
}
}
それにより、Illuminate\Database\Eloquent\Builder
クラスの__call()
クラスが起動されます。
/**
* Dynamically handle calls into the query instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if ($method === 'macro') {
$this->localMacros[$parameters[0]] = $parameters[1];
return;
}
if ($this->hasMacro($method)) {
array_unshift($parameters, $this);
return $this->localMacros[$method](...$parameters);
}
if (static::hasGlobalMacro($method)) {
$callable = static::$macros[$method];
if ($callable instanceof Closure) {
$callable = $callable->bindTo($this, static::class);
}
return $callable(...$parameters);
}
if ($this->model !== null && method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
return $this->callScope([$this->model, $scope], $parameters);
}
if (in_array($method, $this->passthru)) {
return $this->toBase()->{$method}(...$parameters);
}
$this->forwardCallTo($this->query, $method, $parameters);
return $this;
}
そちらにもたくさんの条件に満たされないのでforwardCallTo()
メソッドの$this->forwardCallTo($this->query, $method, $parameters)
により、またIlluminate\Database\Query\Builder
クラスの__call()
メソッドに転送されます。
/**
* Handle dynamic method calls into the method.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (Str::startsWith($method, 'where')) {
return $this->dynamicWhere($method, $parameters);
}
static::throwBadMethodCallException($method);
}
Illuminate\Database\Query\Builder
クラスではメソッドの始まりにwhere
文字がついていた場合、$this->dynamicWhere($method, $parameters)
メソッドにより、動的にwhere
条件が追加されるという仕組みとなっています。
※ 詳しくはdynamicWhere
の中身を確認お願いします。
最後に
個人的にはUser::where('email', '=', 'example@gmail.com')
の方が多く使いますがUser::whereEmail('example@gmail.com')
のメソッドを使っている方も珍しくはありません。
基本的には=
でSQLのwhere句が追加されるようになっているのでこちらの方が分かりやすいという方はぜひ使ってみてください。この記事を読んでいただいて、皆さんの知識が一つでも増えると嬉しいです。