マジックメソッドのPhpDocを自動生成する方法

# はじめに
Laravelにはide-helper（php artisan ide-helper:models）というphpdocを自動生成してくれる便利なライブラリがありますが、ide-helperの対応範囲外のクラスにおいてもこれと同じようなことができないかということを考えてみました。

# 実現したこと
クラス内のプロパティに関するgetメソッドのphpdocを自動生成いたしました。
このことによりgetterメソッドを毎回書く必要がなくなり、getのマジックメソッドを基底クラスに一つ書いておくことでコード全体をすっきりとさせることができます。

# 仕組み
PHPにはクラス情報を取得するReflectionClassという関数があります。
この関数を使うとなんと！PhpDocの情報やプロパティの情報も取得できたりします。
つまり既存PhpDocの内容に対し、プロパティのgetメソッドを追加することもできます。
※ide-helperでもこれと似たような手法でPhpDocの自動生成を行っています

# サンプルコード
```php

/**
 * Class UpdateGetterMethodPhpDoc
 */
class UpdateGetterMethodPhpDoc
{
    /**
     * 指定ファイル内のPhpDocを更新する（プロパティのGetメソッドを追加）
     *
     * @param array $file_paths ファイルパス一覧
     * @param string $filtering_class_name 対象となるクラス名
     */
    public function run(array $file_paths, string $filtering_class_name)
    {
        foreach ($file_paths as $file_path) {
            // 対象となるクラス以外は何もしない
            if (strpos($file_path->getRealPath(), $filtering_class_name) === false) {
                continue;
            }
            
            // ファイル名からクラスを生成し、該当クラス内のプロパティのGetメソッドをPhpDocとして定義する
            foreach (ClassMapGenerator::createMap($file_path->getRealPath()) as $model => $class_file_path) {
                $php_docs = [];
                $reflection = new ReflectionClass($model);
                
                if (empty($reflection->getDocComment())) {
                    throw new LogicException('クラスのPhoDocが未定義です。必ず入力して下さい');
                }
                
                foreach ($reflection->getProperties() as $property) {
                    preg_match('/@var([^\n]*)/', $property->getDocComment(), $variable_data_list);
                    $this->verifyProperty($reflection->getName(), $property, $variable_data_list);
                    [
                        $data_type,
                        $description,
                    ] = explode(' ', ltrim($variable_data_list[1]));
                    $php_docs[] = sprintf('%s %s() %s', $data_type, Str::camel($property->getName()), $description);
                }
                
                $blade_file = view('Component.PhpDoc.base', [
                    'namespace'  => $reflection->getNamespaceName(),
                    'class_name' => $reflection->getShortName(),
                    'php_docs'   => $php_docs,
                ])->render();
                
                $new_file = str_replace($reflection->getDocComment(), rtrim($blade_file), File::get($class_file_path));
                File::delete($class_file_path);
                File::put($class_file_path, $new_file);
            }
        }
    }
    
    /**
     * プロパティが正しく定義されているかどうかの検証
     *
     * @param string $class_name クラス名
     * @param ReflectionProperty $property プロパティ情報
     * @param string[] $variable_data_list プロパティーの@var部分を切り取った情報 [0:キーワード(@var)を含む文字列、1:マッチした部分のみの文字列]
     */
    private function verifyProperty(string $class_name, ReflectionProperty $property, array $variable_data_list)
    {
        if (empty($property->getDocComment())) {
            throw new LogicException(sprintf('%sのプロパティである%sのPhoDocが未定義です。必ず入力して下さい', $class_name, $property->getName()));
        }
        
        if (empty($variable_data_list) || empty($variable_data_list[1])) {
            throw new LogicException(sprintf('%sのプロパティである%sのPhoDocが正しくありません。@var及び型を付けて下さい', $class_name, $property->getName()));
        }
        
        if (strpos($variable_data_list[1], 'mixed') !== false) {
            throw new LogicException(sprintf('%sのプロパティである%sのPhoDocが正しくありません。プロパティの型にmixedは使用しないで下さい', $class_name, $property->getName()));
        }
    }
}

/**
 * Class PhpDocCreateCommand
 */
class PhpDocCreateCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'php_doc_create {target_directory} {filtering_class_name}';
    
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'プロパティ用のGetメソッドのためのphpdocを生成する';
    
    /**
     * プロパティ用のGetメソッドのためのphpdocを生成する
     */
    public function handle()
    {
        $filtering_class_name = $this->argument('filtering_class_name');
        $file_paths = File::allFiles(app_path($this->argument('target_directory')));
        
        $update_getter_method_php_doc = new UpdateGetterMethodPhpDoc();
        $update_getter_method_php_doc->run($file_paths, $filtering_class_name . '.php');
    }
}
```


```base.blade.php
/**
 * Class {{ $class_name }}
 *
 * @package{{ sprintf(' %s',$namespace) }}
@foreach($php_docs as $php_doc)
 * @@method {{ $php_doc }}
@endforeach
 */
```