LaravelのORMがなぜ便利なのかを理解したいと思っていましたが、仕事が忙しくてなかなか時間が取れませんでした。最近自分で何かを作っている際に、呼び出し方を間違えないようにするためにソースコードを調べてみました。
$counter = AdminLog::where('is_delete', GlobalCode::NORMAL);
この静的呼び出しを追跡すると、このクラスは実際には動的メソッドであることがわかりました。
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure && is_null($operator)) {
$column($query = $this->model->newQueryWithoutRelationships());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
継承されたモデルのModelが呼び出すBuilderクラスの__callメソッドを見てみましょう。
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
return $resolver($this);
}
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
/**
* Handle dynamic static method calls into the model.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
Builderクラスの__callメソッドは以下のようになっています。
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->hasNamedScope($method)) {
return $this->callNamedScope($method, $parameters);
}
if (in_array($method, $this->passthru)) {
return $this->toBase()->{$method}(...$parameters);
}
$this->forwardCallTo($this->query, $method, $parameters);
return $this;
}
/**
* Dynamically handle calls into the query instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public static function __callStatic($method, $parameters)
{
if ($method === 'macro') {
static::$macros[$parameters[0]] = $parameters[1];
return;
}
if ($method === 'mixin') {
return static::registerMixin($parameters[0], $parameters[1] ?? true);
}
if (! static::hasGlobalMacro($method)) {
static::throwBadMethodCallException($method);
}
$callable = static::$macros[$method];
if ($callable instanceof Closure) {
$callable = $callable->bindTo(null, static::class);
}
return $callable(...$parameters);
}
核心となるのは__callと__callStaticの相互利用です。
public function __call($method, $parameters)
{
return $this->$method(...$parameters);
}
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
これが核心部分です。
例を見てみましょう。
test.php
<?php
include_once './p.php';
include_once './pp.php';
$a = new pp();
$a->notify(1111);
echo PHP_EOL;
//$a->aa(1111);
//pp::notify(1111);
p.php 親クラス
<?php
class p
{
public function notify($ddd)
{
print_r(get_class($this) . 'notify111111111');
echo PHP_EOL;
}
public static function refund()
{
echo self::class . 'refund';
}
public function __call($method, $parameters)
{
return $this->$method(...$parameters);
}
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
}
pp.php 子クラス
<?php
class pp extends p
{
// public function notify($ddd)
// {
// print_r(get_class($this) . 'notify111111111');
// echo PHP_EOL;
// }
// public static function refund()
// {
// echo self::class . 'refund';
// }
}
注意点:
-
親クラスと子クラスを継承する場合、子クラスのメソッドを呼び出すときは、子クラスのメソッドはprotectedまたはそれ以下のアクセス権でなければなりません。そうしないと警告が表示されます。
-
親クラスと子クラスを継承する場合、メソッドが親クラスのメソッドである場合、親クラスは警告メッセージを表示しません。
-
子クラスまたは現在のクラスにpublicメソッドがある場合、まずpublicメソッドを探し、見つからない場合は__callや__callStaticマジックメソッドを呼び出します。
-
このような設計にする必要がある場合は、クラスのアクセス権に注意してください。さもないと多くの小さな問題が発生します。