サイトアイコン ktykwsk.com

マジックメソッド__call()と__callStatic()

マジックメソッド使ってますか?

私はあまり好きではありませんでした。意図せず呼ばれてしまい、デバッグが困難になってしまうイメージが強かったからです。

しかし、Laravel(Lumen)を使用し、__call() と __callStatic()に触れ、興味が湧いてきました。

マジックメソッドの公式ドキュメント

__call()

__call()の公式ドキュメント

このメソッドは、未定義のインスタンスメソッドを実行した時に呼び出されます。

例を記します。

<?php

class Foo {
  public function __call($name, $arguments) {
    echo "Method name: " . $name;
  }
}

$foo = new Foo();
$foo->bar();

?>

__call()メソッドはインスタンス メソッドとして定義しなければなりません。

実行結果は以下となります。

Method name: bar

このように未定義のインスタンス メソッドを実行した際、__call()メソッドが実行されます。

__callStatic

__callStatic()の公式ドキュメント

このメソッドは未定義のクラス メソッドを実行した際に実行されます。

例を記します。

<?php
class Foo {
  public static function __callStatic() {
    echo "Method name: " . $name;
  }
}

Foo::bar();
?>

__callStatic()メソッドはクラス メソッドとして定義しなければなりません。

このように未定義のクラス メソッドを実行した際、callStatic()メソッドが実行されます。

Laravel(Lumen)での実装例

Illuminate\Database\Eloquent\Model

https://github.com/laravel/framework/blob/5.6/src/Illuminate/Database/Eloquent/Model.php#L1573-L1583

    /**
     * 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);
    }

自身のインスタンスを生成し、引数$methodが示すインスタンス メソッドの実行を試みます。

こうする事で、インスタンス メソッドをクラス メソッドのように実行しています。

https://github.com/laravel/framework/blob/b12feabda70f136cb2d7081af41441a0bf22edc7/src/Illuminate/Database/Eloquent/Model.php#L1557-L1571

    /**
     * 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->newQuery()->$method(...$parameters);
    }

__call()では、まずメソッド名が’increment’、または’decrement’ と一致するか判定します。

一致する場合、increment、またはdecrementメソッドが実行されます。

一致しなかった場合、newQuery()メソッドを実行し、Illuminate\Database\Eloquent\Builderクラスのインスタンスを生成します。そのインスタンスの$methodが示すメソッドを実行します。

これらの実装により、Illuminate\Database\Eloquent\Modelクラスのクラスメソッド としてwhere()、insert()を実行した場合、Illuminate\Database\Eloquent\Builderクラスのインスタンス メソッドが実行されます。

Illuminate\Support\Facades\Facade

https://github.com/laravel/framework/blob/b12feabda70f136cb2d7081af41441a0bf22edc7/src/Illuminate/Support/Facades/Facade.php#L206-L224

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

Facadeは__callStatic()メソッドを利用して、実体となるインスタンスのメソッドを実行しています。

この仕組みを利用して、実体となるクラスを継承し、メソッドを追加、そのクラスのインスタンスをファサードの実体とすることで、ファサード経由で追加したメソッドを呼び出せるようになります。

まとめ

正しく使えば、抽象化にずいぶん貢献できるように感じました。一つの実装案として頭の片隅に記憶しておくとよいでしょう。

モバイルバージョンを終了