2011年2月20日日曜日

PHP Zend_Config_XML使ってみる。

Zend Frameworkを使用してWebアプリケーションを作ることになった。XMLを処理するので、Zend_Config_XMLを使ってXMLをParseしてみたが、少し使いにくい。

まず使い方としては文字列かファイル名を渡して、必要な要素を指定してやる。サンプルとしてはこんな感じ。
$ cat sample_zend_config_xml.php 
<?php
require_once('Zend/Config/Xml.php');

$string = <<< "END"
<?xml version="1.0" ?>
<ResultSets>
  <Result>
    <field>
      <id>1</id>
      <title>hoge</title>
    </field>
    <field>
      <id>2</id>
      <title>fuga</title>
    </field>
  </Result>
</ResultSets>
END;
$zcx = new Zend_Config_Xml($string, 'Result');
var_dump($zcx);
ネットで色々調べると、サンプルとしては大体ファイル名を引数にしているのが多いが、ファイルを分けるのも面倒なので文字列を渡す。「<?xml version="1.0" ?>」をつけるのを忘れなく!!これをつけないと、
$ php sample_zend_config_xml.php 
PHP Fatal error:  Uncaught exception 'Zend_Config_Exception' with message 'simplexml_load_file(): I/O warning : failed to load external entity "
この通りFatalなエラーとなる。良くメッセージを呼べば気づくのだが、文字列じゃなくてファイル用のsimplexml_load_file()が呼ばれているからだ。
 88         if (strstr($xml, '<?xml')) {
 89             $config = simplexml_load_string($xml);
 90         } else {
 91             $config = simplexml_load_file($xml);
 92         }
Zend/Config/Xml.phpのこの行で、文字列を判別して、simplexml_load_stringかsimplexml_load_fileかを呼び分けているからだ。

話が少しそれたのでもどすと、サンプルの実行例は下記の通り。
$ php sample_zend_config_xml.php 
object(Zend_Config_Xml)#1 (9) {
  ["_skipExtends":protected]=>
  bool(false)
  ["_allowModifications":protected]=>
  bool(false)
  ["_index":protected]=>
  int(0)
  ["_count":protected]=>
  int(1)
  ["_data":protected]=>
  array(1) {
    ["field"]=>
    object(Zend_Config)#5 (8) {
      ["_allowModifications":protected]=>
      bool(false)
      ["_index":protected]=>
      int(0)
      ["_count":protected]=>
      int(2)
      ["_data":protected]=>
      array(2) {
        [0]=>
        object(Zend_Config)#3 (8) {
          ["_allowModifications":protected]=>
          bool(false)
          ["_index":protected]=>
          int(0)
          ["_count":protected]=>
          int(2)
          ["_data":protected]=>
          array(2) {
            ["id"]=>
            string(1) "1"
            ["title"]=>
            string(4) "hoge"
          }
          ["_skipNextIteration":protected]=>
          NULL
          ["_loadedSection":protected]=>
          NULL
          ["_extends":protected]=>
          array(0) {
          }
          ["_loadFileErrorStr":protected]=>
          NULL
        }
        [1]=>
        object(Zend_Config)#4 (8) {
          ["_allowModifications":protected]=>
          bool(false)
          ["_index":protected]=>
          int(0)
          ["_count":protected]=>
          int(2)
          ["_data":protected]=>
          array(2) {
            ["id"]=>
            string(1) "2"
            ["title"]=>
            string(4) "fuga"
          }
          ["_skipNextIteration":protected]=>
          NULL
          ["_loadedSection":protected]=>
          NULL
          ["_extends":protected]=>
          array(0) {
          }
          ["_loadFileErrorStr":protected]=>
          NULL
        }
      }
      ["_skipNextIteration":protected]=>
      NULL
      ["_loadedSection":protected]=>
      NULL
      ["_extends":protected]=>
      array(0) {
      }
      ["_loadFileErrorStr":protected]=>
      NULL
    }
  }
  ["_skipNextIteration":protected]=>
  NULL
  ["_loadedSection":protected]=>
  string(6) "Result"
  ["_extends":protected]=>
  array(0) {
  }
  ["_loadFileErrorStr":protected]=>
  NULL
}
なんか色々と要素が追加されているが不要なので、とりあえずtoArrayして使うと下記のような配列が得られる。
$result = $zcx->toArray();
var_dump($result);

array(1) {
  ["field"]=>
  array(2) {
    [0]=>
    array(2) {
      ["id"]=>
      string(1) "1"
      ["title"]=>
      string(4) "hoge"
    }
    [1]=>
    array(2) {
      ["id"]=>
      string(1) "2"
      ["title"]=>
      string(4) "fuga"
    }
  }
}
割と簡単に使えるなと思っていたが、問題が出てきてた。ParseするのがWebAPIの返却するもので要素の数が決まっていなかったからだ。fieldが1つのときだと、
array(1) {
  ["field"]=>
  array(2) {
    ["id"]=>
    string(1) "1"
    ["title"]=>
    string(4) "hoge"
  }
}
このとおり。2つ以上あるfieldはXMLのfield要素の配列であるのに対して、1つの場合はfield要素そのものとなる。
foreach($result['field'] as $field) {
    /*処理*/
}
のようにしていたので、1つの場合はおかしなことに。。。そこで、場当たり的な下記処理を入れる。
if (isset($result['field']['id'])) {
    $result['field'] = array($result['field']);
}
foreach($result['field'] as $field) {
    /*処理*/
}
field要素そのものかを判定して、要素そのものなら配列にしてしまう。という処理。これで一応は問題なく動くようにはなった。


そもそもだが、このクラスはConfigファイルを読むためのクラスなんだろうと今更ながらに思う。だからネットでサンプルを調べるとファイルから読む使い方が多いのだろう。WebAPIの返すようなXMLを読むのは用途違いかもしれない。結局内部的にはsimplexml_load_stringかsimplexml_load_fileを使っているわけだし、simplexml_load_stringを使うように書き直すかな。