tohokuaikiのチラシの裏

技術的ネタとか。

SimpleXMLElementのファイル限界値があったのでPEAR::XMLRPC2を書き換えてみた話

php - How can I handle a huge XML file using SimpleXML but to prevent memory and performance problems? - Stack Overflow
で大きいファイルダメっすよって言われてるのだけど、PEARのXMLRPC2がSimploeXMLElement使ってて、ぐぬぬって思ったので調査

すると、どうも
10000064
で頭打ちみたい。なので、だいたい10,000,000文字、10Mバイト超えたらダメですよってことで。

ところで、DOMDocumentなら100Mくらいは実績値でやってるので行けます。*1

書き換えるかな。・・・ということで、

<?php

class Junoe_XMLRPC_Decorder
{
    /**
     * @brief
     * @param
     * @retval
     */
    public function decode($xml)
    {
        $r = "";
        $dom = new DOMDocument('1.0');
        $dom->loadXML($xml, LIBXML_PARSEHUGE);
        $xpath = new DOMXpath($dom);
        // fault
        $result = $xpath->query('/methodResponse/fault');
        if ($result->length > 0){
            $r = array();
            foreach ($xpath->query('//struct/member', $result->item(0)) as $member){
                $name =  $xpath->query('name', $member)->item(0)->textContent;
                $value = $xpath->query('value', $member)->item(0)->textContent;
                $r[$name] = $value;
            }
            throw XML_RPC2_FaultException::createFromDecode($r);
        }
        else {
            $v_ele = $xpath->query('/methodResponse/params/param/value/*');
            if ($v_ele->length >= 1){
                $r = self::getvalue($xpath, $v_ele->item(0));
            }
        }

        return $r;
    }


    /**
     * @brief
     * @param
     * @retval
     */
    private function getvalue($xpath, $ele)
    {
        $value = "";

        switch (strtolower($ele->tagName)) {
          case 'array':
            $value = array();
            foreach ($xpath->query('data/value/*', $ele) as $vele){
                $value[] = self::getvalue($xpath, $vele);
            }
            break;

          case 'struct':
            $value = array();
            foreach ($xpath->query('member', $ele) as $member){
                $name = $xpath->query('name', $member)->item(0)->textContent;
                $val  = self::getvalue($xpath, $xpath->query('value/*', $member)->item(0));
                $value[$name] = $val;
            }
            break;

          case 'datetime.iso8601':
            throw new Exception('not implemented yet.');
            break;

          case 'boolean':
            $text = $ele->textContent;
            if (strcasecmp($text, "true") === 0 || strcasecmp($text, "1") === 0){
                $value = true;
            }
            if (strcasecmp($text, "false") === 0 || strcasecmp($text, "0") === 0){
                $value = false;
            }
            break;

          case 'double':
            $text = $ele->textContent;
            $value = doubleval($text);
            break;

          case 'i4':
          case 'int':
            $text = $ele->textContent;
            $value = intval($text);
            break;

          case 'string':
            $value = $ele->textContent;
            break;

          case 'nil':
            $value = null;
            break;

          case 'base64':
            $value = new stdClass();
            $text = $ele->textContent;
            $value->scalar = base64_decode($text);
            $value->xmlrpc_type = "base64";
            break;
        }

        return $value;
    }
}

ってClassを1つ作って、./Backend/Xmlrpcext/Client.php のxmlrpc_decode()が使われているところでフック。

<?php
        $result = xmlrpc_decode($body, $this->encoding);
        if (is_null($result)){
            $result = Junoe_XMLRPC_Decorder::decode($body); // in case of $body is larger than 10M byte xmlrpc_decode fail.
        }

*1:ただし、loadXMLの第二引数に LIBXML_PARSEHUGE を付けること。