なんでもアリのhtml_tag関数

https://qiita.com/mHadate/items/41ed113adfcaa1f7607a
上記の記事を読んで、html_tag()関数をちょっと使ってみようかなと思いました。htmlはできるだけviewに直書きしたいのであまりロジカルに作りたくないんですが、プレゼンタ層でガシガシ書かなきゃいけないことも多い。こういう関数が役に立つ場面は多いです。

http://fuelphp.jp/docs/1.9/classes/html.html#/function_html_tag
これですね。FuelPHPにはお世話になっております。

```php

function html_tag($tag, $attr = array(), $content = false)
{
	// list of void elements (tags that can not have content)
	static $void_elements = array(
		// html4
		"area","base","br","col","hr","img","input","link","meta","param",
		// html5
		"command","embed","keygen","source","track","wbr",
		// html5.1
		"menuitem",
	);

	// construct the HTML
	$html = '<'.$tag;
	$html .= ( ! empty($attr)) ? ' '.(is_array($attr) ? array_to_attr($attr) : $attr) : '';

	// a void element?
	if (in_array(strtolower($tag), $void_elements))
	{
		// these can not have content
		$html .= ' />';
	}
	else
	{
		// add the content and close the tag
		$html .= '>'.$content.'</'.$tag.'>';
	}

	return $html;
}

```

上記はFuelPHPで実装されている実際のコード。でもこれって、`textarea` とかどう扱う？ タグが限定され過ぎな気がします。

```php

function html_tag($tag_name, $attrib=array(), $content=null) {
    if(!$attrib && !$content) {
        return sprintf('<%s>', $tag_name);
    }
    if($attrib) {
        foreach($attrib as $k => $v) {
            if($v===null) {
                unset($attrib[$k]);
                continue;
            }
            if($v==='') {
                $attrib[$k] = sprintf('%s', $k);
            } elseif(is_bool($v)) {
                $attrib[$k] = sprintf('%s="%s"', $k, (int)$v);
            } else {
                $attrib[$k] = sprintf('%s="%s"', $k, $v);
            }
        }
    }
    if($content===null) {
        return sprintf('<%s %s>', $tag_name, implode(' ', $attrib));
    }
    return sprintf(
        '<%s %s>%s</%s>'
        , $tag_name
        , $attrib       ? implode(' ', $attrib)  : ''
        , $content!=='' ? htmlspecialchars($content) : ''
        , $tag_name
    );
}
```

自分はこう書きました。 `null`の解釈が @mHadate さんと違っていて、私の解釈では unset します。なぜこうしたかというと、「デフォルトでは存在するけど、個別では不要な場合がある。どうやって削除する？」っていうケースが自分の場合は多いからです。最終的にはブラウザが適当に判定するので、わりとどっちでもいい感じですが。
※投稿後に思ったけど、呼び出し元でunsetすりゃいいだけの話か？ `null` が「入れ物としては存在するが値を持たない」ということなら、やっぱり @mHadate さんの解釈がいい気がする。
※よく読んだら「属性を設定しない場合は`null`にする」と書いてあった。逆だった。

## 追記2 (2019-08-26)

```php
function html_tag($tag_name, $attrib=array(), $content=null) {
    if(!$attrib && !$content) {
        return sprintf('<%s>', $tag_name);
    }
    if($attrib) {
        foreach($attrib as $k => $v) {
            if($v===null) {
                $attrib[$k] = sprintf('%s', $k);
            } elseif(is_bool($v)) {
                $attrib[$k] = sprintf('%s="%s"', $k, (int)$v);
            } else {
                $attrib[$k] = sprintf('%s="%s"', $k, $v);
            }
        }
    }
    if($content===null) {
        return sprintf('<%s %s>', $tag_name, implode(' ', $attrib));
    }
    return sprintf(
        '<%s%s>%s</%s>'
        , $tag_name
        , $attrib       ? implode(' ', $attrib)  : ''
        , $content!=='' ? ' ' . htmlspecialchars($content) : ''
        , $tag_name
    );
}
```

呼び出し元で値を整えるのがリーダブルだと思うので、結局こうしました。`null`の場合は属性名のみ出力します。

select->optgroup->optionみたいなネストで使うことを想定して、もう少しよく考えたほうがいいかも？

## 追記(2019-08-26)

結局、こうしました。

```php
function html_tag($tag_name, $attrib=array(), $content=null) {
    if(!$attrib && !$content) {
        return sprintf('<%s>', $tag_name);
    }
    if($attrib) {
        foreach($attrib as $k => $v) {
            if($v===null) {
                $attrib[$k] = sprintf('%s', $k);
            } elseif(is_bool($v)) {
                $attrib[$k] = sprintf('%s="%s"', $k, (int)$v);
            } elseif($v==='') {
                unset($attrib[$k]);
            } else {
                $attrib[$k] = sprintf('%s="%s"', $k, $v);
            }
        }
    }
    if($content===null) {
        return sprintf('<%s %s>', $tag_name, implode(' ', $attrib));
    }
    return sprintf(
        '<%s%s>%s</%s>'
        , $tag_name
        , $attrib   ? ' ' . implode(' ', $attrib)  : ''
        , $content
        , $tag_name
    );
}
```

関数内で `htmlspecialchars()` するのをやめました。`select->option` というふうに入れ子にしたい時に、`option` がエスケープされてしまうからです。必要な時は呼び出し元でエスケープします。ほぼ必要でしょうけど。

```php
html_tag(
    'option'
    ,array(
        'value'    => $data['value'],
        'selected' => $data['id']==current('id') ? null : ''
    )
    , $data['title']
);
```

上記のように、三項演算子を使ってインラインですっきり書くことができます。unsetしたい時は 空の値つまり ` '' ` を使います。これはこれでどうかという気はしますが・・

とりあえずこれで。プレゼンタ層でhtmlを書かなきゃいけない時は、今までは `sprintf` とか使ってましたけど、この関数を使ってすっきり記述できそうです。

https://qiita.com/mHadate/items/41ed113adfcaa1f7607a
関数の使い方は、上記と全く同じです。自分が作った関数のほうがいいと思うわけでもないけど、違うアプローチで作るのは楽しいので作っちゃいました。
※記事がリーダブルじゃなくてすみません。