Help us understand the problem. What is going on with this article?

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

はじめに

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

実現したこと

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

仕組み

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

サンプルコード

/**
 * 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
 */
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした