テンプレートエンジンを使ってコード生成

# はじめに
如何にしてコーディングを楽にするかを考えることは重要です。
DBスキーマと一対一で作成されているModelみたいな、同じような構造のファイルが並んでいるのを見るとコード生成をしたくなりますね。
そんなとき、自分はスクリプト言語とテンプレートエンジンを使ってちゃっちゃとコード生成スクリプトを組んでいます。

# 使用言語

色々なスクリプト言語はありますが、当記事では下記を使ったコード生成スクリプトについて記載しています。

- php
- twig(テンプレートエンジン)

# Twigテンプレートエンジンとは

軽量・高速なPHPのテンプレートエンジンです。
もとはLaravelなどのフレームワークと組み合わせてHTMLを吐き出すのに使います。(MVCでいうViewの部分)
Viewに渡された変数をView側で変換できる、「フィルター」という機能が便利で、簡潔かつ直感的にテンプレートファイルを記載することができます。
例えば標準フィルターとして搭載されている`upper`はViewに渡された文字列をすべて大文字にします。

```
{# フィルター機能はbashにおけるパイプ('|')のようなイメージ #}
{# 変数の後ろの'|'で区切られているフィルター処理によって随時変換される #}
{# 変数name内に例えば'apple'が渡された場合、下記の出力は'APPLE'になる #}
{{ name|upper }}
```

Twigには大量の便利な標準フィルターが搭載されています。

- 参考
    - https://twig.symfony.com/doc/3.x/

さらに独自のフィルターを作成することもできます。

# 実践


## 目的
例えば下記のようなCSVファイルがあるとして、物理名からモデルファイルを生成したい場合を考えます。
プロパティとして`$id`、`$name`、`$num`があり、そのgetterとsetterが存在するphpクラスファイルを生成するイメージです。

```csv
id,name,num
1,りんご,3
2,みかん,5
3,さくらんぼ,7
```

## 準備

composerとtwigのインストールを行っておきます。

- composerのインストール
    - https://getcomposer.org/download/

- twigのインストール

```php
php composer.phar require "twig/twig:^3.0"
```

- 今回用いるディレクトリ構成は以下

```
.
├── composer.json
├── composer.lock
├── composer.phar
├── data
│   └── test.csv
├── main.php
├── output
├── template
│   └── model.tpl
└── vendor
    └── ...
```

## コード

生成したいソースコードのテンプレートファイルを作成し、phpファイルに向けて書き出すことでコード生成を成立させます。
`ucfirst`は最初の文字を大文字にするカスタムフィルターです。(twigの標準フィルターには存在しなかったため作成しています。)

- main.php

```php
<?php

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

use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;

$loader = new FilesystemLoader(dirname(__FILE__) . '/template');
$twig = new Environment($loader);

// カスタムフィルター定義
$twig->addFilter(
    new TwigFilter(
        'ucfirst',
        function (string $str) {
            return ucfirst($str);
        }
    )
);

// csv読み込み(物理名取得)
$fp = fopen("./data/test.csv", "r");
$columns = fgetcsv($fp);
fclose($fp);

// ソースコード吐き出し
$template = $twig->load("model.tpl");
file_put_contents(
    "./output/test.php",
    $template->render(["className" => "test", "columns" => $columns])
);

```

- model.tpl

```none
<?php

namespace Path/To/Model;

class {{className|ucfirst}}
{

{% for column in columns %}
    /**
     * @var string
     */
    private string ${{column}};

{% endfor %}

    /**
     * {{className}} constructor.
{% for column in columns %}
     * @param string ${{column}}
{% endfor %}
     */
    public function __construct(
{% for column in columns %}
        string ${{column}}{% if not loop.last %},{% endif %}

{% endfor %}
    )
    {
{% for column in columns %}
        $this->{{column}} = ${{column}};
{% endfor %}
    }

{% for column in columns %}
    /**
     * @return string
     */
    public function set{{column|ucfirst}}(): string
    {
        return $this->{{column}};
    }

{% endfor %}

{% for column in columns %}
    /**
     * @return string
     */
    public function get{{column|ucfirst}}(): string
    {
        return $this->{{column}};
    }

{% endfor %}
}

```

## 実行

`main.php`を実行すると下記のようなファイルがoutputディレクトリに吐き出されます。

- test.php

```php
<?php

namespace Path/To/Model;

class Test
{

    /**
     * @var string
     */
    private string $id;

    /**
     * @var string
     */
    private string $name;

    /**
     * @var string
     */
    private string $num;


    /**
     * test constructor.
     * @param string $id
     * @param string $name
     * @param string $num
     */
    public function __construct(
        string $id,
        string $name,
        string $num
    )
    {
        $this->id = $id;
        $this->name = $name;
        $this->num = $num;
    }

    /**
     * @return string
     */
    public function setId(string $id): string
    {
        return $this->id = $id;
    }

    /**
     * @return string
     */
    public function setName(string $name): string
    {
        return $this->name = $name;
    }

    /**
     * @return string
     */
    public function setNum(string $num): string
    {
        return $this->num = $num;
    }


    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getNum(): string
    {
        return $this->num;
    }

}

```

# おわりに

CSVのスキーマから対応するコード生成を行う簡単なスクリプトを組みました。
テンプレートエンジンを使うことの利点は、構造情報取得ロジックの部分を変えずに、吐き出すソースコードの形を自由に変えられることだと思います。
ロジックとView(コードテンプレート)を切り分けることで、すばやくメンテのし易いコード生成スクリプトを作ることができるのではないでしょうか。