PHPのDOMを使いやすくする

## はじめに


PHPは標準でDOMが使えますが、とても使いにくく、あまり使われていません。
機能が不足していたり、冗長な書き回しになったり、とにかく不便です。

そこで、PHPのDOMを使いやすくするために、自作のプログラムを作りました。
Web標準であるDOMに慣れ親しんだ人なら、簡単に使えるものを目標としています。

技術的には、PHPの [DOMDocument](https://www.php.net/manual/ja/class.domdocument.php) と [DOMElement](https://www.php.net/manual/ja/class.domelement.php) を**拡張**したものになっています。

## HTMLの入力

最初にベースとなるHTML文字列を渡して document オブジェクトを作成します。

```php
$document = new document('<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title></title></head><body></body></html>');
```

- 引数省略時は内蔵テンプレートが使用されます。上記サンプルと同じHTMLです
- HTMLは、全体を1つのタグで囲っておく必要があります


## HTMLの出力

documentオブジェクトは自動的に文字列に変換されるので、出力するだけでHTMLを表示できます。


```php
$document = new document();

print $document; // HTMLを表示する

file_put_contents('example.html', $document); // HTMLをファイルに保存する

$html = "$document"; // 文字列としてコピーする
```
DOM要素も同様に、自動的に文字列に変換されます

```php
print $document->body; // <body></body> が表示される
```


## 主要タグの短縮形

主要タグの`<html>` `<head>` `<body>` `<title>`には短縮形があり、`$document->タグ名`でアクセスできます。

```php
$document->body  // $document->getElementsByTagName('body')[0] の短縮形

$document->title->textContent = 'タイトル名'; //titleはweb標準と異なるので注意
```

この値は**DOM要素**です。

## id付タグの短縮形

idが付いているタグには`$document->id名`でアクセスできます。

```php
$document->exapmle  // $document->getElementById('example') の短縮形
```
この値は**DOM要素**です。存在しなければnull

操作したいタグにはあらかじめidを付けておくと、綺麗なコードが書けます。


## 属性アクセスの短縮形

属性へのアクセスは短縮形があり、`$el->属性名`と書けます。

```php
$body = $document->body;

$body->id            // $body->getAttribute('id') の短縮形
$body->id = 'value'  // $body->setAttribute('id', 'value') の短縮形
unset($body->id)     // $body->removeAttribute('id') の短縮形
isset($body->id)     // $body->hasAttribute('id') の短縮形
```
## タグの検索

### セレクタで検索

documentオブジェクトには、JavaScriptでお馴染みの`querySelector`があります。
CSSセレクタを使用して、ドキュメント全体からタグを検索できます。


```php
//単数検索
$document->querySelector('CSSセレクタ')    //戻り値はDOM要素またはnull

//複数検索
$document->querySelectorAll('CSSセレクタ') //戻り値は常に配列
```


### 対応セレクタ

|検索対象|セレクタ|
|:--:|:--|
|タグ名|a|
|id|#a|
|class|.a|
|属性名|[a]|
|属性名と値|[a="b"]|
|and|ab|
|子孫|a b|
|子|a > b|
|弟|a + b|
|弟全て|a ~ b|
|全て|*|

### querySelector()の短縮形

`querySelector()`には短縮形が用意されていて、jQuery風に書けます。`$document('セレクタ')`

```php
$document('.example')   // $document->querySelector('.example') の短縮形
```

デフォルト動作は**単数検索**です。複数検索するには、セレクタの頭に`*`を付けます。

```php
$document('*.example')  // $document->querySelectorAll('.example') の短縮形
```

## タグの作成

### タグを1つ作成する短縮形

タグの作成には短縮形があり`$document(<タグ名>, 本文, [属性名=>属性値])`で1つタグが作成できます。
戻り値は**DOM要素**で、本文と属性は省略できます。

```php
// <div class="example">sample</div> を作る
$document('<div>', 'sample', ['class'=>'example']); 

// 次のコードの短縮形
// $div = $document->createElement('div');
// $div->textContent = 'sample';
// $div->setAttribute('class', 'example');
```

### 子タグも一括作成

`<ul>` `<ol>` `<select>` `<table>` のタグは、本文を配列で渡すと、子タグも一括して作成できます。

```php:ulタグ
$document('<ul>', [1,2], ['id'=>'example']);
//<ul id="example">
//  <li>1</li>
//  <li>2</li>
//</ul>
```

```php:selectタグ
$document('<select>', [1,2]);
//<select>
//  <option value="1">1</option>
//  <option value="2">2</option>
//</select>
```

```php:tableタグ
$document('<table>', [1,2]);
//<table>
//  <tr><td>1</td></tr>
//  <tr><td>2</td></tr>
//</table>

$document('<table>', [[1,2], [3,4]]);
//<table>
//  <tr><td>1</td><td>2</td></tr>
//  <tr><td>3</td><td>4</td></tr>
//</table>
```

## HTML文字列をDOM化

HTML文字列を一括してDOM化するには`$document('HTML文字列')`で可能です。
コンポーネントの読み込みに適しています。

```php
$component = $document('<nav><a id="nav_link">Home</a></nav>'); // コンポーネントの読み込み

$component->nav_link->href = 'http://example.jp/'; // id短縮形が使える。querySelectorもある 

$document->body->appendChild($component); // <body>の最後に<nav>を挿入する
```

他の方法として`$el->innerHTML`でHTML文字列を代入することもできます。

## [付録] DOM操作の早見表

DOMの代表的な操作をまとめてみました。
DOMに不慣れな人でも次の早見表を使えば大丈夫👍


```php
//タグを検索する
$document->querySelector('セレクタ');    // 戻り値はDOM要素またはnull
$document->querySelectorAll('セレクタ'); // 戻り値は配列

//タグを作成する
$el = $document->createElement('タグ名');// 戻り値はDOM要素


//中身を取得する
$el->textContent; //戻り値はタグを含まない文字列

//中身を取得する
$el->innerHTML;   //戻り値はタグを含む文字列


//中身を変更する
$el->textContent = '文字列はエスケープされる';

//中身を変更する
$el->innerHTML   = '文字列はエスケープされない';

//中身を全て削除する
$el->textContent = '';


//タグ名を取得する
$el->tagName; //戻り値は小文字

//属性を取得する
$el->getAttribute('属性名'); //戻り値は文字列

//属性を設定する
$el->setAttribute('属性名', '属性値');

//属性を削除する
$el->removeAttribute('属性名');


//※タグを子の最後に追加する
$el->append($new_el);

//※タグを子の最初に追加する
$el->prepend($new_el);

//※タグを前に追加する
$el->before($new_el);

//※タグを後ろに追加する
$el->after($new_el);


//※タグを置換する
$el->replaceWith($new_el);

//※タグを削除する
$el->remove();

//タグをコピーする
$el->cloneNode(true); //戻り値はDOM要素


//タグの子孫からタグを検索する
$el->querySelector('セレクタ');    //戻り値はDOM要素またはnull
$el->querySelectorAll('セレクタ'); //戻り値は配列
```

上記のコードで※マークが付いているものはPHP8.0～向けの書き方です。
PHP7以下では次のように書きます。

```php
//タグを子の最後に追加する
$el->appendChild($new_el);

//タグを子の最初に追加する
$el->insertBefore($new_el, $el->firstChild);

//タグを前に追加する
$el->parentNode->insertBefore($new_el, $el);

//タグを後ろに追加する
$el->parentNode->insertBefore($new_el, $el->nextSibling);

//タグを置換する
$el->parentNode->replaceChild($new_el, $el);

//タグを削除する
$el->parentNode->removeChild($el);
```



## ソースコード

[ダウンロード](https://gist.github.com/worldace/48c8453c90954bf29ade0a74da0ab714)

PHPファイルを`include`するだけで使えます。ご自由にお使いください。