YAML+Smartyでコードを自動生成する
先のPEAR::Services_Recruit_Abroad作ってみた - Do You PHP はてなですが、アクセサ(getter/setter)があまりに多く、手書きするのはちょっと現実的ではないなぁ、と思ってました。PHP対応のIDEを使っていれば自動生成もできるんでしょうが、コメントを手書きすることなども含めると、どうもなぁ。。。となってしまいます。
ということで、今回はYAML+Smartyでコードを生成するバッチをちょこっと作ってみました。
仕様としては、以下の通りです。
- 生成されたコードはコピペして使うことを前提
- YAMLを扱うため、syck拡張モジュールを使う
- YAMLファイルにメンバー変数を定義する
- メンバー変数名
- メンバー変数の型(array/intのみ)
- 型によってアクセサ(getter/setter)を作り分ける
- コードの雛形はSmartyのテンプレートで定義する
- メインスクリプトでYAMLファイルを読み込み、Smartyにassign・fetch。fetchした内容をfile_get_contentsで出力
で、コードの方ですが、まずYAMLファイルから。このデータから作成されたコードは、先のPEAR::Services_Recruit_Abroadに含まれるAirline.phpで使っています。
properties: airline: description: "a airline name" type: array keyword: description: "your search keyword" type: array start: description: "the number of the first row" type: int count: description: "the row number of fetching data" type: int
次はテンプレートファイル。サクッと作るために、{litetal}が多くなっちゃってます;-)
<?php {foreach from=$properties item=property key=key} /** * {$property.description} * @var {$property.type} * @access private */ private ${$key}; {/foreach} /** * constructor * * @param string $apikey the apikey string * @return void * @access public */ public function __construct($apikey) {literal}{{/literal} parent::__construct($apikey); {foreach from=$properties item=property key=key} {if $property.type === 'array'} $this->{$key} = array(); {else} $this->{$key} = null; {/if} {/foreach} } /** * build the Query-String string * * @return string built the Query-String string * @access protected */ protected function buildParameters() {literal}{{/literal} return '' . {foreach from=$properties item=property key=key} $this->build{$key|ucfirst}Parameters() . {/foreach} ''; } {foreach from=$properties item=property key=key} /** * build the 'count' part of Query-String string * * @return string the 'count' part of Query-String string * @access protected */ protected function build{$key|ucfirst}Parameters() {literal}{{/literal} $params = ''; {if $property.type === 'array'} foreach ($this->get{$key|ucfirst}() as $key => $dummy) {literal}{{/literal} $params .= '&{$key}=' . $key; } {else} if (!is_null($this->get{$key|ucfirst}())) {literal}{{/literal} $params .= '&{$key}=' . $this->get{$key|ucfirst}(); } {/if} return $params; } {/foreach} {foreach from=$properties item=property key=key} {if $property.type === 'array'} /** * replace the large category codes * * @param array $code an array of the large category codes * @return void * @access public */ public function put{$key|ucfirst}(array $code) {literal}{{/literal} $this->{$key} = $code; } /** * add the large category code * * @param string $code the large category code * @return void * @access public */ public function add{$key|ucfirst}($code) {literal}{{/literal} $this->{$key}[$code] = true; } /** * remove the large category code * * @param string $code the large category code * @return void * @access public */ public function remove{$key|ucfirst}($code) {literal}{{/literal} if (isset($this->{$key}[$code])) {literal}{{/literal} unset($this->{$key}[$code]); } } {else} /** * set {$property.description} * * @param {$property.type} {$key} {$property.description} * @return void * @access public */ public function set{$key|ucfirst}(${$key}) {literal}{{/literal} {if $property.type === 'int'} ${$key} = $this->validateDigit(${$key}); {/if} $this->{$key} = ${$key}; } {/if} /** * return {$property.description} * * @return type {$property.description} * @access public */ public function get{$key|ucfirst}() {literal}{{/literal} {literal}return{/literal} $this->{$key}; } {/foreach} /** * validate if the given value is digit, and return the int value * * @param string $param a parameter * @return int a int value of the given parameter * @access private * @throws Exception throws when the given parameter is not digit */ private function validateDigit($param) {literal}{{/literal} if (!preg_match('/^\d+$/', $param)) {literal}{{/literal} throw new Exception('Invalid format "' . $param . '"'); } return (int)$param; }
<?php require_once 'Smarty/Smarty.class.php'; $YAML_FILENAME = './data/airline_property.yml'; $smarty = new Smarty(); $smarty->template_dir = './'; $smarty->compile_dir = './templates_c'; $contents = file_get_contents($YAML_FILENAME); $properties = syck_load($contents); $smarty->assign('properties', $properties['properties']); $contents = $smarty->fetch('code.tpl'); echo $contents; file_put_contents(basename($YAML_FILENAME) . '_code.php', $contents);
まあ、コード自体は全然難しくないですね :-)
データの作成がちょっと面倒なので実質手間的に変わらない気もしますが、データから自動生成することで「テンプレートに従った間違いのないコード」を作ることができます。よく言われる「楽をする」というメリットよりは、この「間違いのないコード」が作成できる=手戻りが発生しないというのが一番のメリットではないかと思います。まあ、テンプレートが間違っていれば当然テンプレートを修正する必要はありますが、再作成も当然自動なので大した問題にはならないと考えています。
また、HTML出力ではよくお目にかかるSmartyですが、個人的にはこういった何らかのコード・キュメントの出力やメール本文の作成でよく使います。結構便利ですよ。