2012年12月6日木曜日

PHPで「かっこういい」HTMLタグ生成クラス

2012年PHPアドベントカレンダー6日目です。

ちょっと前に、HTMLフォーム要素を動的に出力する必要が出てきて、調べても使いやすそうなクラスが見つからなかったので自分で作ったものです。

思った以上に「格好良い」ものができたので、広報してみることにしました。

ソースコード:https://github.com/asaokamei/Tags

コンストラクション


$tags = new Tags();

依存性ゼロです。簡単に使えます。

簡単なサンプル


早速、簡単な使い方の例を見てみます。
echo $tags->a( 'link' )->href( 'tags.php' );
// <a href="tags.php">link</a>

なにか、いい感じですよね!?
タグをネストしてみます。

$ul = $tags->ul(
  $tags->li( 'list #1' ),
  $tags->li( $tags->img()->src( 'img.gif' ) ),
  'just a text'
);
echo $ul;
/*
<ul>
  <li>list #1</li>
  <li><img src="img.gif" /></li>
  just a text
</ul>
*/

htmlタグと同じように使えます。

なおimgの場合はタグで囲まないようになってます。

基本的な使い方



使い方は、
最初にHTMLタグ名のメソード名にして、
後はアトリビュート名をチェーンしてゆきます。

$tags->tag( '中身' ) -> attr1( val1 ) -> attr2( val2 );
// <tag attr1="val1" attr2="val2">中身</tag>

基本、タグ一個につき、オブジェクト一つ作ります。

imgやinputなどのタグは、終端タグを出力しないようになっています。

少し複雑なサンプル


最初にTagsオブジェクトを作っておいて、後から中身を追加することもできます。複雑になりがちなoptgroupのあるSelect文を作ってみます。

$lang = array(
//  array( value, option name, optgroup ), ...
    array( 'zhi',  'chinese',   'asia'   ),
    array( 'jpn',  'japanese',  'asia'   ),
    array( 'kor',  'korean',    'asia'   ),
    array( 'eng',  'english'             ),
    array( 'fra',  'french',    'europe' ),
    array( 'ger',  'german',    'europe' ),
    array( 'spa',  'spanish',   'europe' ),
);
$select = $tags->select()->name( 'language' );
$groups = array();
foreach( $lang as $item ) // ループ languages.
{
    $option = $tags->option( $item[1] )->value( $item[0] );
    if( isset( $item[2] ) ) // has an optgroup.
    {
        if( !isset( $groups[ $item[2] ] ) ) { // 新規 optgroup.
            $groups[ $item[2] ] = $tags->optgroup()->label( $item[2] );
            $select->contain_( $groups[ $item[2] ] );
        }
        $groups[ $item[2] ]->contain_( $option );
    }
    else {
        $select->contain_( $option );
    }
}
echo $select;
/*
<select name="language">
  <optgroup label="asia">
    <option value="zhi">chinese</option>
    <option value="jpn">japanese</option>
    <option value="kor">korean</option>
  </optgroup>
  <option value="eng">english</option>
  <optgroup label="europe">
    <option value="fra">french</option>
    <option value="ger">german</option>
    <option value="spa">spanish</option>
  </optgroup>
</select>
 */

中身を追加する際は、contain_メソードを使います。

タグがオブジェクトのままなので、後から好きなだけ中身を追加できます。
昔書いたoptgroupのコードに比べて、短くすっきりできました。

オブジェクトなので


後から中身を強制的に変更することもできます。


$input = $tags->input()->type( 'check' )->name( 'checkMe' )->value( 'Yeap' );
echo $input;
$input->walk( function( $tags ) {
    if( isset( $tags->attributes['name'] ) ) $tags->attributes['name'].='[]';
} );
echo $input . "\n";
/*
<input type="check" name="radioMe" value="Yeap" />
<input type="check" name="radioMe[]" value="Yeap" />
 */

walkというメソードは、変数内のTagsオブジェクトをすべて走査して、クロージャーを適用します。これが理由で、多くのプロパティはpublicだったりします。

例は、nameアトリビュートに「[]」を追加して、配列に変えています。

コードについて少し解説


中身はどうなっているのか?
PHPのマジックメソードを使っています。

public function __call( $name, $args )
{
    // attribute or tag if not set.
    if( is_null( $this->tagName ) ) { // set it as a tag name
        return new static( $name, $args );
    }
    else {
        $this->setAttribute_( $name, $args );
    }
    return $this;
}

と言っても、それほど変なことはしてなくて、

  1. 自分自身にtagNameが設定されていなければ、新しくオブジェクトを作ってタグ名を設定して、返しています。
  2. tagNameが設定されていれば、アトリビュートとして追加して、自分自身を返しています。


なかなか気に入ったものが出来ず、試行錯誤しましたが、一番最初のメソード名をタグ名と考えることに気がついたことで使いやすくなりました。欲しいHTMLと同じ順番でコードをかけるので、迷わず使えます。

文字列への変換する部分です。

public function __toString()
{
    return $this->toString_();
}

オブジェクトが文字列に変換する際には、__toStringメソードが呼ばれるので、そこでHTMLタグとして文字列を返しています。

この先のコードは・・・
出力をキレイにしようとすると、コードが汚くなる不思議。

参考サイト


php-XML_Builder
php-class-html-generator

使っている技法は「メソードチェーン」と呼ばれるものですが、DSL(ドメイン・スペシフィック・ランゲージ)を作っているという考えかたもあるようです。

それで検索して見つけたのが、このプレゼンテーション
使い方は、まだ簡単になりそうですね。

0 件のコメント: