【PHP】ArrayToXmlの使い方


# ArrayToXmlのインストール方法
```shell:composer
    composer require spatie/array-to-xml
```

# サンプルのXML
簡単な例として以下のXMLを組み立てて行きます。
```xml
    <?xml version="1.0" encoding="UTF-8"?>
    <foods>
        <vegetable>tomato</vegetable>
        <fruits>apple</fruits>
        <meat>beef</meat>
    </foods>
```
# サンプルコード
```ArrayToXml```では```convert()```メソッドに配列を渡すことでXMLを文字列で返します。
また、連想配列で各要素を定義していく為、全体像を把握しやすくなります。
```convert()```メソッドには引数が多い為、PHP8.0から追加された名前付き引数で記述していますが省略して大丈夫です。
```php
    use Spatie\ArrayToXml\ArrayToXml;
    
    $elements = [
        'vegetable' => 'tomato',
        'fruits'    => 'apple',
        'meat'      => 'beef'
    ];

    ArrayToXml::convert(
        array: $elements,
        rootElement: 'foods',
        replaceSpacesByUnderScoresInKeyNames: true,
        xmlEncoding: 'UTF-8',
        xmlVersion: '1.0',
        domProperties: ['formatOutput' => true]
    );
```
```text:実行結果
"""
<?xml version="1.0" encoding="UTF-8"?>\n
<foods>\n
  <vegetable>tomato</vegetable>\n
  <fruits>apple</fruits>\n
  <meat>beef</meat>\n
</foods>\n
""" 
```
#  ArrayToXml::convert()の引数
||データ型|仮引数名|内容|
|-|-|-|-|
|第1引数|array|$array|root以下の構成要素の配列|
|第2引数|mixed|$rootElement|root要素に関する配列、またはroot要素名の文字列|
|第3引数|bool|$replaceSpacesByUnderScoresInKeyNames|タグ名に空白がある場合、`_`に変更するかを```true```, ```false```で指定(省略可)|
|第4引数|string|$xmlEncoding|生成されるXMLのエンコーディングを指定(省略可)|
|第5引数|string|$xmlVersion|生成されるXMLのバージョンを指定(省略可)|
|第6引数|array|$domProperties|内包されている```DOMDocument```オブジェクトのプロパティを設定(省略可)|
|第7引数|bool|$xmlStandalone|生成されるXMLの```standalone```属性を```ture```,```false```で指定(省略可)|
|第8引数|bool|$addXmlDeclaration|XML宣言文を含めるかを```ture```,```false```で指定(省略可)|
|第9引数|array|$options|XML生成時の細かい挙動の設定(省略可)|


# 各仮引数の使用例

### 第1引数 $arrayについて
XMLのルート要素(サンプルでいうところの```<foods></foods>```)以下に入る要素を記述します。
シンプルな構造の場合と、複雑な構造の場合では記述方法が変わります。
シンプルな構造とは初めのサンプルコードで出した例です。

#### 1\. 各要素に属性がついている場合

```xml:例
    <?xml version="1.0" encoding="UTF-8"?>
    <foods>
        <vegetable id="1">tomato</vegetable>
        <fruits id="2">apple</fruits>
        <meat id="3">beef</meat>
    </foods>
```
この場合は、その要素に関する配列を作成して対応します。
```_attributes```に各属性名と値の連想配列を追加します。値は```_value```で指定します。
```php
    $elements = [
        'vegetable' => [
            '_attributes' => ['id' => '1'],
            '_value'      => 'tomato'
        ],
        'fruits' => [
            '_attributes' => ['id' => '1'],
            '_value'      => 'apple'
        ],
        'meat' => [
            '_attributes' => ['id' => '1'],
            '_value'      => 'beef'
        ],
    ];

    ArrayToXml::convert($elements, 'foods');
```

#### 2\. 同じタグ名が複数ある場合
```xml:例
    <?xml version="1.0" encoding="UTF-8"?>
    <foods>
        <vegetable>tomato</vegetable>
        <vegetable>carrot</vegetable>
        <vegetable>onion</vegetable>
    </foods>
```
配列に```vegetable```要素を3回定義すればいけそうに見えますが、PHPでは連想配列に同じキーを指定できません。
最終的には出力されるのは最後に定義している```onion```だけになります。
```vegetable```キーに配列を渡すことで同じタグ名の要素を複数作成することが可能です。
```php
    $elements = [
        'vegetable' => [
            'tomato',
            'carrot',
            'onion'
        ]
    ];

    ArrayToXml::convert($elements, 'foods');
```
または以下のようにキー名を```__custom:タグ名:任意の文字列```と定義することで対応可能です。
任意の文字列は重複しないように指定してください。文字列、数値で揃える必要や連番で揃える必要はありません。
```php
    $elements = [
        '__custom:vegetable:1' => 'tomato',
        '__custom:vegetable:2' => 'carrot',
        '__custom:vegetable:3' => 'onion'
    ];

    ArrayToXml::convert($elements, 'foods');
```
注意点として```__custom:```を使用する際、タグ名に```:```(コロン)を入れたい場合は```:```(コロン)の前に```\\```バックスラッシュを2個記述することでエスケープできます。


#### 3\. 入れ子構造の場合
例で出してきたサンプルでは全ての要素がルート要素直下でしたが、XMLが入れ子構造になっている場合もあります。
```xml:例
    <?xml version="1.0" encoding="UTF-8"?>
    <foods>
        <vegetable>
            <name>tomato</name>
            <price>150</price>
        </vegetable>
        <fruits>
            <name>apple</name>
            <price>100</price>
        </fruits>
        <meat>
            <name>beef</name>
            <price>200</price>
        </meat>
    </foods>
```
このような場合は連想配列も入れ子構造にすることで対応可能です。
```php
    $elements = [
        'vegetable' => [
            'name' => 'tomato',
            'price' => 150,
        ],
        'fruits' => [
            'name' => 'apple',
            'price' => 100,
        ],
        'meat' => [
            'name' => 'beef',
            'price' => 200
        ]
    ];

    ArrayToXml::convert($elements, 'foods');
```

### 第2引数 $rootElementについて
第2引数ではroot要素に関する情報を渡します。
サンプルのXMLのようにroot要素がただ単に名前のみの場合は文字列で渡します。
サイトマップのようにroot要素に属性を持たせる場合などは配列を渡します。

```xml:例
    <?xml version="1.0" encoding="UTF-8"?>
    <foods country="US">
        <vegetable>tomato</vegetable>
        <fruits>apple</fruits>
        <meat>beef</meat>
    </foods>
```

root要素の名前は配列内の```rootElementName```で指定し、属性は```_attributes```で指定します。
```php
    $elements = [
        'vegetable' => 'tomato',
        'fruits'    => 'apple',
        'meat'      => 'beef'
    ];

    $rootElement = [
        'rootElementName' => 'foods',
        '_attributes'     => [
            'country' => 'US',
        ],
    ];

    ArrayToXml::convert($elements, $rootElement);
```

### 第3引数 $replaceSpacesByUnderScoresInKeyNamesについて
第3引数では``true``にした場合、タグ名の空白を```_```に変換するかを指定します。
省略した場合は```true```になります。
ただし、タグ名に空白があり、```false```を選択した場合、例外が発生します。
内包されている```DOMDocument```の```DOMException```がスローされます。
```php
        $elements = [
            'red color fruits' => 'apple'
        ];
        
        ArrayToXml::convert($elements, 'foods', false);
```
```text:実行結果
    DOMException
    Invalid Character Error
```
その為、```true```を指定する以外選択肢はなく、明示的に```true```を指定するためだけの引数のような感じだと思います。

### 第4引数 $xmlEncodingについて
第4引数ですが、ここではエンコードを指定します。
省略可能ですが、省略した場合はエンコードの情報がないXMLを生成します。
```UTF-8```か```Shift_JIS```を使用することが多い印象です。

### 第5引数 $xmlVersionについて
第5引数ではXML宣言文内のバージョンの指定を指定します。
```xml
    <!--  ↓ここの部分↓ -->
    <?xml version="1.0" encoding="UTF-8"?>
```
省略可能で、省略した場合のバージョンは1.0になります。
存在しないXMLのバージョンを指定した場合でもエラーなどは特に起きません。

### 第6引数 $domPropertiesについて
第6引数では内包されている`DOMDocument`の設定を配列で渡します。
基本的には```formatOutput```で十分です。
```formatOutput```では整形して出力するかを`true`,`false`で指定します。

#### $domPropertiesを省略、またはformatOutputがfalseの場合
```php
    $elements = [
        'fruits' => 'apple'
    ];

    ArrayToXml::convert(
        array: $elements,
        rootElement: 'foods',
        domProperties: [
            'formatOutput' => false,
        ]
    );
```
```text:実行結果
"""
<?xml version="1.0"?>
<foods><fruits>apple</fruits></foods>
"""
```
#### formatOutputがtrueの場合
インデント、改行などを加えて整形してくれます。
```php
    $elements = [
        'fruits' => 'apple'
    ];

    ArrayToXml::convert(
        array: $elements,
        rootElement: 'foods',
        domProperties: [
            'formatOutput' => true,
        ]
    );

```
```text:実行結果
"""
<?xml version="1.0"?>
<foods>
  <fruits>apple</fruits>
</foods>
"""
```

### 第7引数 $xmlStandaloneについて
第7引数ではXML宣言文にstandalone属性を追加できます。
```true```の場合は```standalone="yes"```になり
```false```の場合は```standalone="no"```になります。
```xml
                　<!--  ↓ここの部分↓ -->
    <?xml version="1.0" standalone="yes"?>
```

### 第8引数 $addXmlDeclarationについて
第8引数ではXML宣言文を含めるかを指定します。
省略可能で、省略時は```true```になります。
```false```を指定することでroot要素以下のみが出力されます。

### 第9引数 $optionsについて
第9引数はXML生成時の詳細な制御ができます。省略可能で省略時は`convertNullToXsiNil`が```false```になります。
|キー|内容|
|-|-|
|convertNullToXsiNil|値がnullの場合、 ```xsi:nil="true"```にするかどうかを指定。また、root要素に```xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"```が追加されます。|
|convertBoolToString|値がboolの場合、PHPでは```true```が1に```false```が0になるが、それを```true```,```false```として扱うかを指定。|

どちらともtrueを指定した場合のサンプルです。
```php
    $elements = [
        'profile'    => null,
        'is_active'  => true,
    ];

    ArrayToXml::convert(
        array: $elements,
        rootElement: 'user',
        domProperties: ['formatOutput' => true],
        options: [
            'convertNullToXsiNil'  => true,
            'convertBoolToString'  => true,
        ]
    );
```
```text:実行結果
"""
<?xml version="1.0"?>
<user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <profile xsi:nil="true"></profile>
  <is_active>true</is_active>
</user>
"""
```
# 所感
PHPでXMLを組み立てる方法を調べても、```DOMDocument```や```SimpleXMLElement```はよく見るものの```ArrayToXml```について言及している記事を見つけられなかったので書いてみました。
XMLを組み立てる際の参考になると幸いです。