汎用ツリー構造クラスを作ってみた

何かとツリー構造が必要な場合があって、今までは配列で頑張ってました。
そろそろ、何とかしたいと思っいたってググってみても、探し方が悪いのかいい感じのものは見つからず。
そこで、面倒なのでサクッと作ってみました。
他にいいのがあれば教えてください。

# ソース

```php:TreeNode.php
/**
 * 汎用ツリー構造クラス
 */
<?php
class TreeNode
{
    /** 本オブジェクトの親要素 */
    private $parent;

    /** 本オブジェクトの子要素 */
    private $children = [];

    /** 名前 */
    private $name;

    /** 値 */
    public $value;

    /**
     * コンストラクタ
     *
     * @param string $name 名前(親の場合は省略可能)
     */
    public function __construct($name = null)
    {
        $this->name = $name;
    }

    /**
     * 子を追加する
     * すでに同名のものがある場合は追加しない
     *
     * @param string $name 追加する子要素
     * @return TreeNode 追加した子要素
     */
    public function append($name)
    {
        $child = $this->child($name);
        if ($child === false) {
            $child = new TreeNode($name);
            $child->parent = $this;
            $this->children[$child->name] = $child;
        }

        return $child;
    }

    /**
     * 指定した子を削除する
     *
     * @param string $name 削除する子要素
     * @return TreeNode 自分自身
     */
    public function remove($name)
    {
        if ($this->has($name)) {
            unset($this->children[$name]);
        }
        return $this;
    }

    /**
     * 指定した名前の子要素を取得する
     *
     * @param string $name 名前(省略時は全部)
     * @return TreeNode | array | boolean
     */
    public function child($name = null)
    {
        // 引数省略時は全部
        if (is_null($name)) {
            return $this->children;
        }

        // 引数指定時は指定した名前の子要素
        if (!$this->has($name)) {
            return false;
        }
        return $this->children[$name];
    }

    /**
     * 指定した名前の子要素があるかどうか
     * 省略時は子要素自体があるかどうか
     *
     * @param string $name 名前
     * @return boolean
     */
    public function has($name = null)
    {
        // 引数省略時
        if (is_null($name)) {
            return (0 < count($this->children));
        }
        return isset($this->children[$name]);
    }

    /**
     * 名前を取得
     * @return string
     */
    public function name()
    {
        return $this->name;
    }

    /**
     * 親要素を取得
     *
     * @return TreeNode 親要素
     */
    public function parent()
    {
        return $this->parent;
    }

    /**
     * パスの配列を取得する
     * valueは含まれません
     * 例: [NULL, 'HOGE', 'FUGA']
     *
     * @return array
     */
    public function path()
    {
        $result = [$this->name];

        $parent = $this->parent;
        while($parent != null) {
            $result[] = $parent->name;
            $parent = $parent->parent;
        }
        $result = array_reverse($result);
        return $result;
    }

}
```

# 使い方

```php
 // 親ノード作成
$tree = new TreeNode();

// 子ノードを追加して子の値を設定
// 親 > HOGE = 123
$tree->append('HOGE')->value = 123;

// 子ノードを追加して、更にその下に子ノードを追加して
// 親 > HOGE > FUGA
$tree->append('HOGE')->append("FUGA");

// 子ノードHOGEの下にある子ノードFUGAを取得
$fuga = $tree->child('HOGE')->child('FUGA');

// 子ノードHOGEを追加して、それを削除(親ノードしか残ってない)
$tree->append('HOGE')->parent()->remove('HOGE')

// 「/」区切りのパス文字列を取得(/HOGE/FUGA)
echo implode('/', $tree->append('HOGE')->append('FUGA')->path());
```

# おまけ

文字列から階層化されたものを作成してくれるメソッド追加

```php
/**
 * 文字列をセパレータで分割したものをtree情報化する
 *
 * @param char $separator セパレータ文字
 * @param string $string 文字列
 * @return TreeNode TreeNode親要素
 */
public static function fromString($separator, $string)
{
    // セパレータが特殊文字の場合はエスケープ
    $sep = $separator;
    if ($sep == '/' || $sep == '\\') {
        $sep = '\\' . $sep;
    }

    // 先頭のセパレータを除去
    $string = preg_replace('/^' . $sep . '/', '', $string);

    $list = explode($separator, $string);

    $tree = null;
    foreach ($list as $str) {
        if ($tree === null) {
            $tree = new TreeNode($str);
        } else {
            $tree = $tree->append($str);
        }
    }
    return $tree;
}
```

## 使い方

```php
$tree = TreeNode('/', 'hoge/fuga');
var_dump($tree->path())
// array(2) { [0]=> string(4) "hoge" [1]=> string(4) "fuga" }

$tree = TreeNode('/', '/hoge/fuga');
var_dump($tree->path())
// array(3) { [0]=> string(0) "" [1]=> string(4) "hoge" [2]=> string(4) "fuga" }
```