PHPリフレクションを用いた動的メソッド呼び出し

# はじめに
業務でリフレクションを用いることがあったので、備忘録としてリフレクションの実装例を紹介する記事です。
PHPではリフレクションというものを利用することで、クラスのメソッドやプロパティに動的にアクセスすることが可能です。クラスやメソッドを動的に操作でき、状況に応じて異なるメソッドを呼び出したい場合に非常に役立ちます。

# リフレクションとは？
リフレクションは、クラスやメソッド、プロパティについてのメタ情報（プログラムの構造、特性、および挙動）を取得し、それを操作するPHPの機能です。PHPでは ReflectionClass や ReflectionMethod を使って、実行時に動的にクラスやメソッドを操作できます。

# 実装をしてみる
早速実装していきます。
本記事では、サービスとリポジトリを分割し、リフレクションを用いた動的メソッド呼び出しを実装する例を紹介します。

まず、リポジトリクラスで具体的なメソッドを定義し、それをサービスクラスからリフレクションで呼び出す設計を実装してみます。

#### リポジトリクラス
```php

<?php

namespace App\Repositories;

class ExampleRepository
{
    public function methodA()
    {
        return "methodAが呼ばれます";
    }

    public function methodB()
    {
        return "methodBが呼ばれます";
    }
}

```
シンプルにするため、methodA、methodBともに、呼ばれたらそれぞれ文章を返すだけのメソッドにしています。

次に、上記リポジトリメソッドを、動的に呼び出すためのリフレクションの実装をサービスクラスで行っていきます。

#### サービスクラス
```php

<?php

namespace App\Services;

use App\Repositories\ExampleRepository;
// リフレクションクラスをuse
use ReflectionClass;

class ExampleService
{
    
    private ExampleRepository $repository; 
    private ReflectionClass $reflection;

    public function __construct(ExampleRepository $repository)
    {
        $this->repository = $repository;
        // リポジトリクラスをリフレクションで扱うための初期化を行う
        $this->reflection = new ReflectionClass(get_class($this->repository));
    }

    public function callRepositoryMethod(string $methodName)
    {
        // 指定されたメソッドが存在するかを確認
        if ($this->reflection->hasMethod($methodName)) {
            $method = $this->reflection->getMethod($methodName);
            return $method->invoke($this->repository);
        } else {
            throw new \Exception("$methodName メソッドは存在しません");
        }
    }
}

```

このサービスクラスでは、コンストラクタでリポジトリを受け取り、それをリフレクションで初期化しています。callRepositoryMethod メソッドで、動的にリポジトリ内のメソッドを呼び出す処理を行っています。

コンストラクタでの初期化について

```php
$this->reflection = new ReflectionClass(get_class($this->repository)); 
```

では、リポジトリクラスのリフレクションオブジェクトを生成しています。
引数で指定している下記

```php
get_class($this->repository) 
```

は、リポジトリクラスのクラス名を取得します。
これにより、$this->reflection には ExampleRepository クラスに関するメタデータが格納されます。

#### callRepositoryMethod メソッドについて
```php

public function callRepositoryMethod(string $methodName)
{
    // 指定されたメソッドが存在するかを確認
    if ($this->reflection->hasMethod($methodName)) {
        $method = $this->reflection->getMethod($methodName);
        return $method->invoke($this->repository);
    } else {
        throw new \Exception("$methodName メソッドは存在しません");
    }
}

```
このメソッドは、リポジトリ内のメソッドを動的に呼び出すための機能を実装しています。
```php
$this->reflection->hasMethod($methodName)
```
ここでは、リポジトリクラスに指定されたメソッドが存在するかを確認します。メソッドが存在する場合は true を返し、存在しない場合は false を返します。

```php
$method = $this->reflection->getMethod($methodName); 
```
getMethodの引数には、呼び出したいリポジトリのメソッド名が入ってくるようにします。
$methodには、指定されたメソッド名に対応する ReflectionMethod オブジェクトが入ります。

```php
$method->invoke($this->repository); 
```
ここで、指定されたメソッドを実際に呼び出します。
このとき、$this->repository を第一引数として渡すことで、そのリポジトリ上でメソッドが実行されます。

これがどういう結果になるのか。
上で定義したリフレクションを実際に使用してみます。

```php

<?php

use App\Services\ExampleService;
use App\Repositories\ExampleRepository;

$repository = new ExampleRepository();
$service = new ExampleService($repository);

try {
    echo $service->callRepositoryMethod('methodA');
    echo $service->callRepositoryMethod('methodB');
    echo $service->callRepositoryMethod('methodC'); // 例外がスローされる
} catch (\Exception $e) {
    echo $e->getMessage();
}

```
callRepositoryMethodnoの引数に、呼び出したいメソッド名を文字列で渡すと、リポジトリで該当するメソッドを呼び出すことができます。

上記の結果は下記のようになります
```
"methodAが呼ばれます"
"methodBが呼ばれます"
"methodC は存在しません"
```

以上が、リフレクションの実装例になります。
ちなみに、例ではinvokeを使用しましたが、invokeArgs を使うと、引数を配列で指定してメソッドを呼び出すことができます。

#### invokeArgsを使った例

```php

<?php

class ExampleClass
{
    public function sayHello($name, $greeting)
    {
        return "$greeting, $name!";
    }
}

$example = new ExampleClass();
$reflection = new ReflectionClass($example);

// `sayHello` メソッドの ReflectionMethod オブジェクトを取得
$method = $reflection->getMethod('sayHello');

// invokeArgs を使ってメソッドを呼び出し、引数を配列で渡す
$result = $method->invokeArgs($example, ['Alice', 'Hello']);

echo $result; 

```

# どういう時に使うの？
CLIアプリケーションで、ユーザーが入力したコマンドに応じて異なるメソッドを実行する場合など
下記のような感じです。

```php

<?php

class CommandHandler
{
    public function start()
    {
        return "プロセスをスタートします";
    }

    public function stop()
    {
        return "プロセスを停止します";
    }

    public function restart()
    {
        return "プロセスを再開します";
    }
}

class CommandLineApp
{
    private $handler;
    private $reflection;

    public function __construct()
    {
        $this->handler = new CommandHandler();
        $this->reflection = new ReflectionClass($this->handler);
    }

    public function runCommand($command)
    {
        if ($reflection->hasMethod($command)) {
            $method = $reflection->getMethod($command);
            return $method->invoke($this->handler);
        } else {
            throw new Exception("$command コマンドが見つかりません");
        }
    }
}

// 使用例
$app = new CommandLineApp();

try {
    echo $app->runCommand('start');    // "プロセスをスタートします"
    echo $app->runCommand('stop');     // "プロセスを停止します"
    echo $app->runCommand('restart');  // "プロセスを再開します"
    echo $app->runCommand('unknown');  // 例外がスローされる
} catch (Exception $e) {
    echo $e->getMessage();  // "Command unknown not found."
}

```

このように、ユーザーの入力やリクエストに基づいて異なる処理を行う必要がある場合などに利用できます。

# まとめ
業務でリフレクションを利用する機会があったので、備忘録として今回の記事を書きました。
リフレクションを用いた動的なメソッド呼び出しは、一見便利そうですがコードの複雑性が高まるリスクもあることや、リフレクションを頻繁に使用することで通常のメソッド呼び出しよりもパフォーマンス低下につながる可能性があります。必要な箇所を見極めることが重要です。