結論から言いますと、__invoke
にしかできないことがあります。それはクロージャにクラスの機能を持たせるということです。
クロージャとinvokeの書き方の違いをサンプルで確認
まずクロージャ(無名関数)とマジックメソッド__invoke
のコードを見てみましょう。どちらも関数を引数として渡すために使っています。
<?php
// difference_closure_invoke.php
function run(callable $before_func)
{
$before_func();
echo 'run' . PHP_EOL;
}
// クロージャ(無名関数)
$before = function () {
echo 'anonymous before' . PHP_EOL;
};
run($before);
// => anonymous before
// run
// invokeのおかげでインスタンスを関数呼出しできます。
// その時に実行されるメソッドが__invokeです。
class Before
{
public function __invoke()
{
echo 'class before' . PHP_EOL;
}
}
run(new Before);
// => class before
// run
クロージャとinvokeの使い分けの基準
今回はどちらもやっていることは同じです。関数を渡すだけならクロージャの方が手軽だと思います。関数を引数として渡すなら下記の使い分けが出来ると思います。
- その場で書き捨てのコールバック関数なら、クロージャを使う。
- 他でも再利用するコールバック関数なら、クラス(
__invoke
)を使う。
クロージャを再利用しようとすると、変数にバインドしないといけません。
<?php
$before = function () {
echo 'anonymous before' . PHP_EOL;
};
このファイルをrequire
した時に、既に$before
という変数がある場合は上書きしまいますし、$before
がグローバル変数になってしまいます。
しかし、クラスで用意しておけば、run(new Before);
のように変数にバインドする必要もありません。バインドするとしてもその変数のスコープはローカルに留めることが出来ます。
これで関数が他のクラスと同じ扱いで、再利用が可能になりました。
invokeしかできないこと
__invoke
を実装しているのはクラスです。クラスということはプロパティやメソッドを持つことができます。__invoke
の処理が長い時にはprivate
メソッドで分割することもできますし、インスタンスを関数呼出しせずに通常のオブジェクトとして使うこともできます。
クロージャ
にクラスとしての機能をつけたい時に__invoke
を使うといいでしょう。関数呼び出しが可能なオブジェクトを、引数として渡した後に通常のオブジェクトとして利用している例です。
<?php
// invoke_as_normal_object.php
class Before
{
private $request;
public function __invoke($request)
{
$this->request = $request;
}
public function getRequest()
{
return $this->request;
}
}
function run($request, callable $before_func)
{
$before_func($request);
// ビジネスロジック...
$response = new StdClass;
return $response;
}
$request = new StdClass;
$request->from = 'japan';
$before = new Before();
$response = run($request, $before);
echo $before->getRequest()->from;
// => japan
run
の中の最初の時点での$request
をrun
が終わった後に、$before
を通じて取り出しています。これだけなら、run
に渡す$request
を使えばよく特に意味は無いです。
ですが、このようにコールバックに渡ってきたオブジェクトや、コールバック内の結果を、関数が終わった後にコールバックオブジェクトを通じて取得したりできます。これを利用してできそうなことを考えてみます。
コンストラクタの代わりにコールバックで値をセットする
下記はHTTPクライアントを使って、取得したページ内容から必要な値だけをオブジェクトにセットする例です。架空のクラスを使用しています。
<?php
class PageItem
{
private $title; // タイトルタグ
private $lead; // 記事の最初の140文字
public function __invoke($response)
{
// HTTPレスポンスの中身をセレクタによって取得する
$this->title = $response->find('title');
$this->title = $response->find('.article > .body > .lead');
}
public function getTitle()
{
return $title;
}
public function getLead()
{
return $lead;
}
}
$page_item = new PageItem();
// httpリクエストを送り、レスポンスを取得することができるクラス
$client = new HttpClient();
$request_uri = 'http://example.com';
$client->get($request_uri, $page_item);
echo 'title: ' . $page_item->getTitle() . PHP_EOL;
echo 'lead: ' . $page_item->lead() . PHP_EOL;
この場合だと、ページを取得した時点ではないと必要な値が分からないため、コンストラクタで値のセットはできません。そこで__invoke
を使ってみました。
ミドルウェアはクラスで作るといいのでは?
より好ましいミドルウェアにより、ルートフィルターは非推奨となりました。
cite: アップグレードガイド 5.1 Laravel
Laravelは5.1からミドルウェアが推奨されました。Slim 3でもミドルウェアがあります。
※ 私はどちらのフレームワークも使ったことはありません。
ミドルウェアとはアクションメソッドをラッピングするクロージャのことです。クロージャの中でアクションメソッドを好きな位置で呼び出せるようになるので、その前後に好きな処理を書くことが出来ます。
また、ミドルウェアをミドルウェアでラッピングするすることができるので、ログイン処理のレイヤー、IPアドレスで弾くレイヤーなど、アクションメソッドまでの処理をレイヤーとして分けて書くことができます。
ミドルウェアでクロージャが使えるということは、__invoke
を実装したクラスも使えますね。再利用しないとしても、スコープが小さく留めたまま好きに書けるので、保守もしやすいですし書きやすいです。そのためレイヤーごとにクラスを用意していくのに__invoke
は使えるのではないかなと思いました。