PHPでメソッドの挙動を柔軟に変更するプロキシ設計

## 概要

プロキシを挟んでメソッドを実行することで処理の主体を切り替え、状況に応じたメソッドのラッピングを簡単にします。

```php
// 普通に走る
echo $dog->run(); // => "1"

// 脚の性能 ($dog固有の値) に依存して走る
echo $dog->withLegs()->run(); // => "4"

// 吠える
$dog->bark(); // => "わん"

// 3回吠える
$dog->repeat(3)->bark(); // => "わんわんわん"

// 別のオブジェクト、別のメソッドでも使える
$cat->repeat(2)->mew(); // => "にゃーにゃー"

// お腹が空いているのなら食べる
$dog->when(function ($dog) {
    return $dog->isHungry();
})->eat(); // => "ばくばく"

// もうお腹がいっぱいになったので食べない
$dog->when(function ($dog) {
    return $dog->isHungry();
})->eat(); // => eat() is not executed.

// public でないメソッド (Dog::grow()) を実行
echo $dog->getAge(); // => "0"
try {
    $dog->grow();
} catch (\Throwable $e) {
    echo $e->getMessage(); // => "Call to private method Dog::grow() from context ''"
}
$dog->unprotect()->grow();
echo $dog->getAge(); // => "1"
```

## サンプルコード

ここで紹介するコードのソースは GitHub に上げてあります。動作を確かめたい場合は `git clone` して `composer install` してから実行してください (PHP 7.1以上) 。

https://github.com/KNJ/php-object-proxy

```bash
git clone git@github.com:KNJ/php-object-proxy.git
cd php-object-proxy
composer install
./vendor/bin/phpunit tests
```

### サンプルの実行コマンド

- `php running.php`
- `php repeating.php`
- `php judgement.php`
- `php unprotecting.php`
- `./vendor/bin/phpunit tests`

※ 序論が長いので手っ取り早く実装を知りたい方は[応用例](#応用例)とサンプルのソースコードを参照してください。

- [指定した回数だけ繰り返しメソッドを実行する](#指定した回数だけ繰り返しメソッドを実行する)
- [条件にマッチしたらメソッドを実行する](#条件にマッチしたらメソッドを実行する)
- [public でないメソッドを外部から実行する](#public-でないメソッドを外部から実行する)

## メソッドの挙動を変更するときの普通の方法

あるメソッドのふるまいを少しだけ変えたい場合には引数を与えてパラメータの変更ができるようにするでしょう。

例: `Animal::run()` はスピードを変更して実行できる。

```php:sample/Running/Animal.php
<?php

namespace Sample\Running;

class Animal
{
    public function run($speed = 1)
    {
        // run at a speed of $speed
        return $speed;
    }
}
```

また、継承したクラスに依存するふるまいを加えたい場合にはオーバーライドをすることでしょう。

例: `Dog` は足の数だけ速くなる (現実はそうではないと思いますが) 。

```php:sample/Running/Dog.php
<?php

namespace Sample\Running;

class Dog extends Animal
{
    const LEGS = 4;

    public function run($speed = 1)
    {
        $speed = $speed * self::LEGS;
        
        return parent::run($speed);
    }
}
```

では、同じように `Human` というクラスも `Animal` を継承して作ってみましょう。

例: `Human` は足の数だけ速くなる。

```php:sample/Running/Human.php
<?php

namespace Sample\Running;

class Human extends Animal
{
    const LEGS = 2;

    public function run($speed = 1)
    {
        $speed = $speed * self::LEGS;
        
        return parent::run($speed);
    }
}
```

おや？ `Dog` のコピペみたいですね。この調子で `Cat` とか `Bear` とか作ったらえらいことになりそうです…。であれば `Animal::run()` は最初から `LEGS` に依存させましょうか？しかし、 `Animal` が哺乳類以外も含めるのであれば `Snake` はどうすれば… (そもそも"走る"のでしょうか？) 。よし、 `Animal` を継承した `Mammal` を作って `run()` をオーバーライドし、 `Mammal` を継承した `Dog` や `Human` を作りましょう！でも多重継承って、本当にいいんでしょうか？う～ん…。あー。 (ここで腕を組んで目をつむる)

## メソッドをまったく別のオブジェクトに委譲してしまうという方法

悩んでいても埒が明かないので、 `run()` は `LEGS` を利用する場合と利用しない場合があると視点を変えてみましょう。脚があってもそれを使わずに移動する動物もいます (小学生の時に観察したハナムグリの幼虫には大変な関心を抱いたものです) 。 `LEGS` を利用する・しないは選択できるようにしましょう。そして、 `LEGS` を利用して `run()` する場合、 `Legs` というクラスに `run()` を代理させます。

もう少しわかりやすく説明しましょう。犬がただ走るのであればそれは「__犬が走る__」となります。しかし、犬が脚を使って走るのであれば主体は脚に切り替わり「__脚が走る__」ということになります。このことをコードに書き出してみましょう。

まず、脚があるやつとないやつを区別しなければならないので次のようなインターフェイスを用意します。

```php:sample/Running/WithLegs.php
<?php

namespace Sample\Running;

interface WithLegs
{
    // legs() は脚の数を返します.
    public function legs(): int;
}

```

脚は__主体を代理する__ことと、__走る__ことができるのでその2つのメソッドを用意します。当然主体は脚を有していることが前提なので `WithLegs` を実装したオブジェクトしか代理できません。このような代理をするオブジェクトをプロキシと呼ぶことにします。

```php:sample/Running/Legs.php
<?php

namespace Sample\Running;

final class Legs
{
    private $animal;

    // represent は主体のオブジェクトを代理します.
    public function represent(WithLegs $animal)
    {
        $this->animal = $animal;

        return $this;
    }

    // これが実質 Animal::run() のオーバーライドになります.
    public function run($speed = 1)
    {
        $legs = $this->animal->legs();

        return $this->animal->run($speed * $legs);
    }
}
```

脚を使って走れるやつは `CanRunWithLegs` というトレイトを導入します。 `withLegs()` というメソッドによりプロキシ化します。

```php:sample/Running/CanRunWithLegs.php
<?php

namespace Sample\Running;

trait CanRunWithLegs
{
    public function withLegs()
    {
        $proxy = new Legs;

        return $proxy->represent($this);
    }
}
```

そしてインターフェイスを実装した `Dog` に変更します。

```php:sample/Running/Dog.php
<?php

namespace Sample\Running;

class Dog extends Animal implements WithLegs
{
    use CanRunWithLegs;

    const LEGS = 4;

    public function legs(): int
    {
        return self::LEGS;
    }
}
```

さあ、実際に使ってみましょう！

```php:running.php
<?php

require_once __DIR__.'/vendor/autoload.php';

$dog = new \Sample\Running\Dog;

// 犬が走る
echo $dog->run() // => "1"
    .PHP_EOL;

// 犬の脚が走る
echo $dog->withLegs()->run() // => "4"
    .PHP_EOL;
```

https://github.com/KNJ/php-object-proxy/tree/master/sample/Running

メソッドの挙動を変更する方法として、以上のように引数の変更や継承してオーバーライドする以外の方法を示しました。こうすることのメリットは、オリジナルのメソッドも呼び出せたり、オリジナルを上書きしていることがわかりやすいことです。

プロキシ化するとプロキシに定義されているメソッド以外は使えなくなってしまいます (まったく別のオブジェクトなのだから当然) 。以下の応用例ではマジックメソッド `__call()` を使ってそれを解決します。

## 応用例

### 指定した回数だけ繰り返しメソッドを実行する

```php:repeating.php
<?php

require_once __DIR__.'/vendor/autoload.php';

$dog = new \Sample\Repeating\Dog;
$cat = new \Sample\Repeating\Cat;

echo $dog->bark() // => "わん"
    .PHP_EOL;

echo $dog->repeat(3)->bark() // => "わんわんわん"
    .PHP_EOL;

echo $cat->repeat(2)->mew() // => "にゃーにゃー"
    .PHP_EOL;

```

プロキシに引数を持たせて挙動を細かく制御し、さらに `__call()` を実装してあらゆるメソッドをプロキシが実行できるようにしています。またトレイトさえ導入すればどんなオブジェクトでもプロキシ化できます[^1]。

プロキシの実装:

```php:sample\repeating\repeater.php
<?php

namespace Sample\Repeating;

final class Repeater
{
    private $original;

    private $times;

    // represent は主体のオブジェクトを代理します.
    public function represent($original, $times)
    {
        $this->original = $original;
        $this->times = $times;

        return $this;
    }

    public function __call($method, $args)
    {
        for ($i = 0; $i < $this->times; $i++) {
            $this->original->$method(...$args);
        }
    }
}
```

https://github.com/KNJ/php-object-proxy/tree/master/sample/Repeating

### 条件にマッチしたらメソッドを実行する

```php:judgement.php
<?php

require_once __DIR__.'/vendor/autoload.php';

$dog = new \Sample\Judgement\Dog;

$dog->when(function ($dog) {
    return $dog->isHungry();
})->eat(); // => ばくばく

$dog->when(function ($dog) {
    return $dog->isHungry();
})->eat(); // => eat() is not executed.
```

`if` を使いたくない人向け。上記の例では示せていませんが、一時的な変数を使用してもそのスコープをクロージャ内に閉じ込められるというメリットがあります。

プロキシの実装:

```php:sample/Judgement/Judge.php
<?php

namespace Sample\Judgement;

use Closure;

final class Judge
{
    private $original;

    private $closure;

    // represent は主体のオブジェクトを代理します.
    public function represent($original, Closure $closure)
    {
        $this->original = $original;
        $this->closure = $closure;

        return $this;
    }

    public function __call($method, $args)
    {
        $closure = $this->closure;

        if ($closure($this->original)) {
            $this->original->$method(...$args);
        }
    }
}
```

https://github.com/KNJ/php-object-proxy/tree/master/sample/Judgement

### public でないメソッドを外部から実行する

```php:unprotecting.php
<?php

require_once __DIR__.'/vendor/autoload.php';

$dog = new \Sample\Unprotecting\Dog;
echo $dog->getAge().PHP_EOL; // => "0"

try {
    $dog->grow();
} catch (\Throwable $e) {
    echo $e->getMessage() // => "Call to private method Sample\Unprotecting\Dog::grow() from context ''"
        .PHP_EOL;
}

$dog->unprotect()->grow();
echo $dog->getAge().PHP_EOL; // => "1"
$dog->unprotect()->grow(2);
echo $dog->getAge().PHP_EOL; // => "3"
```

ユニットテストとかで private or protected なメソッドをテストしたいときの黒魔術詠唱をプロキシで簡略化しちゃいます。

プロキシの実装:

```php:sample/Unprotecting/Unprotector.php
<?php

namespace Sample\Unprotecting;

final class Unprotector
{
    private $original;

    // represent は主体のオブジェクトを代理します.
    public function represent($original)
    {
        $this->original = $original;

        return $this;
    }

    public function __call($method, $args)
    {
        $closure = function ($original, $method, $args) {
            $original->$method(...$args);
        };
        $fn = $closure->bindTo($this->original, $this->original);

        return $fn($this->original, $method, $args);
    }
}
```

https://github.com/KNJ/php-object-proxy/tree/master/sample/Unprotecting

参考: [PHP: Closure::bindTo - Manual ](http://php.net/manual/ja/closure.bindto.php)

ただしこれをテストで使いたい場合、 トレイトが導入されていることが前提になるため、テストのためだけに多くのクラスで use trait 宣言をすることになってしまいます。このような場合はヘルパー関数でプロキシ化する手段を用意しておくとよいでしょう。

```php:tests/bootstrap.php
<?php

require_once __DIR__.'/../vendor/autoload.php';

// public でないメソッドにアクセス可能にするプロキシ化
function unprotect($original)
{
    $proxy = new class($original)
    {
        public function __construct($original)
        {
            $this->original = $original;
        }

        public function __call($method, $args)
        {
            $closure = function ($original, $method, $args) {
                $original->$method(...$args);
            };
            $fn = $closure->bindTo($this->original, $this->original);

            return $fn($this->original, $method, $args);
        }
    };

    return $proxy;
}
```

テストコード: `unprotect($this->dog)->grow()` という形で使います。

```php:tests/Unprotecting/DogTest.php
<?php

declare(strict_types=1);

namespace Tests\Unprotecting;

use PHPUnit\Framework\TestCase;

final class DogTest extends TestCase
{
    public function setUp()
    {
        $this->dog = new \Sample\Unprotecting\Dog;
    }

    public function testGrowMethod()
    {
        // Dog::$age を参照可能にする.
        $age = new \ReflectionProperty(\Sample\Unprotecting\Dog::class, 'age');
        $age->setAccessible(true);

        // private なメソッド Dog::grow() を3回実行して
        // Dog::$age が 0 -> 1 -> 2 -> 3 と増えることをテストする.
        for ($i = 0; $i < 3; $i++) {
            $this->assertSame($i, $age->getValue($this->dog));
            unprotect($this->dog)->grow();
        }

        $this->assertSame(3, $age->getValue($this->dog));
    }
}

```

- https://github.com/KNJ/php-object-proxy/blob/master/tests/bootstrap.php
- https://github.com/KNJ/php-object-proxy/blob/master/tests/Unprotecting/DogTest.php

### 他の応用アイデア

- メソッドの返り値をいろいろなフォーマットに変換する (JSON, シリアル化, ハッシュ値, 暗号化など) 
- メソッド実行中に発行されたSQLを出力する
- メソッドの実行時間やメモリ使用量を計測する

など、応用できる範囲はとても広いと思います。他にもアイデアがあったらコメントをください！

## 余談

Laravel には HigherOrderCollectionProxy という、今回解説したプロキシ設計に似たような概念が Collection 操作のために実装されています。`each` や `map` などのメソッドをプロパティとして呼び出すとプロキシ化し、要素の1つ1つが次に呼び出すメソッドの主体になり反復処理が行われます。

実例を見たほうが早い:

- [Laravel 5.4 – Higher Order Messaging for Collections - Laravel News](https://laravel-news.com/higher-order-messaging)
- [【5.5対応】Laravel の Collection を使い倒してみたくなった 〜 サンプルコード 115 連発 3/3 - Qiita](https://qiita.com/nunulk/items/e92d34c4092f202e60fa#108-proxy)
- [Laravel 5.4で追加された「Higher Order Messages」がすごい - Qiita](https://qiita.com/acro5piano/items/01c999cf2cda81d83136)

## 次回予告 (近日予定)

さらに実用的な応用例として次回は Laravel における Repository パターンとキャッシュの連携を紹介したいと思います。


```php
// UserRepository から "KNJ" という名前のユーザー情報を取得する.
$user = $userRepo->findByName('KNJ');

// UserRepository から "KNJ" という名前のユーザー情報を取得したときの結果をキャッシュする.
$userRepo->saveCache()->findByName('KNJ');

// UserRepository のキャッシュから "KNJ" という名前のユーザー情報を取得する.
$user = $userRepo->useCache()->findByName('KNJ');

// UserRepository から "KNJ" という名前のユーザー情報を取得したときの結果のキャッシュを消去する.
$userRepo->deleteCache()->findByName('KNJ');
```

[^1]: 制限を設けたい場合は `sample/Running/Legs.php` のように `represent()` の仮引数をインターフェイスでタイプヒントすればよいでしょう。