<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="http://blog.xole.net/rss/style.css" type="text/css"?>
<rdf:RDF xmlns="http://purl.org/rss/1.0/"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:content="http://purl.org/rss/1.0/modules/content/"
         xmlns:dc="http://purl.org/dc/elements/1.1/"
         xml:lang="ja">
<channel rdf:about="http://blog.xole.net/rss/1.0.php?id=588">
<title>ハタさんのブログ(復刻版)</title>
<link>http://blog.xole.net/index.php</link>
<dc:date>2007-08-07T00:51:11+09:00</dc:date>
<description>
ハタさんのブログ(復刻版) - RSS (RDF Site Summary).
</description>
<items>
<rdf:Seq>
<rdf:li rdf:resource="http://blog.xole.net/article.php?id=588" />
</rdf:Seq>
</items>
</channel>
<item>
<title>SoapServerのclassmapオプションとtypemapオプションに関する覚書</title>
<link>http://blog.xole.net/article.php?id=588</link>
<dc:date>2007-08-07T00:51:11+09:00</dc:date>
<description>ふと気がつくと、SoapServerのオプションが増えていたので、それの覚書
ref - http://php.net/soap


SoapServerのオプション(コンストラクタoption)についてはマニュアルを確認してもらうと...</description>
<content:encoded>
<![CDATA[
<p>ふと気がつくと、SoapServerのオプションが増えていたので、それの覚書<br />
ref - <a href="http://php.net/soap">http://php.net/soap</a>
</p>

<p>SoapServerのオプション(コンストラクタoption)については<a href="http://jp2.php.net/manual/ja/function.soap-soapserver-construct.php">マニュアルを確認</a>してもらうとして、SoapServerのオプションにある、classmapとtypemapについて忘れない内に書いておく<br />
ちなみに、このclassmapとtypemapはWSDLモードでしか動かないです。</p>

<p>SoapServerのコンストラクタでは、次のような引数とともにオプションを渡すことが可能です。<br />
今回はこの<code>classmap</code>と<code>typemap</code>について少し</p>

<pre class="php">
<span class="vars" >$server</span> = <span class="keyword" >new</span> SoapServer(<span class="vars" >$wsdl</span>, <span class="keyword" >array</span>(
    <span class="string" >'encoding'</span> =&gt; <span class="string" >'UTF-8'</span>,
    <span class="string" >'version'</span> =&gt; SOAP_1_2,
    <span class="string" >'uri'</span> =&gt; <span class="string" >'urn:HogeNamespace'</span>,
    <span class="string" >'actor'</span> =&gt; <span class="string" >'urn:HogeNameSpace'</span>,
    <span class="string" >'typemap'</span> =&gt; <span class="keyword" >array</span>(
        <span class="keyword" >array</span>(
            <span class="string" >'type_ns'</span> =&gt; <span class="string" >'urn:HogeNameSpace'</span>,
            <span class="string" >'type_name'</span> =&gt; <span class="string" >'keyElement'</span>,
            <span class="string" >'from_xml'</span>  =&gt; <span class="keyword" >array</span>(<span class="string" >'SoapTypeUtils'</span>, <span class="string" >'toKeyElement'</span>)
        )
    ),
    <span class="string" >'classmap'</span> =&gt; <span class="keyword" >array</span>(
        <span class="string" >'keyElement'</span> =&gt; <span class="string" >'MyKeyEntity'</span>
    )
));
</pre>

<h3>classmapオプション</h3>
<p>classmapオプションはその名のまま、指定されたclassにmappingを行うオプションです。<br />
たとえば、次のようなrequestのをマッピングするには、classmapオプションは次のようになります。</p>

<pre class="php">
<span class="keyword" >class</span> MyKeyEntity {
    <span class="keyword" >public</span> <span class="vars" >$user</span>;
    <span class="keyword" >public</span> <span class="vars" >$key</span>;
    <span class="keyword" >public</span> <span class="vars" >$timestamp</span>;
}

<span class="vars" >$server</span> = <span class="keyword" >new</span> SoapServer(<span class="vars" >$wsdl</span>, <span class="keyword" >array</span>(
    <span class="string" >'encoding'</span> =&gt; <span class="string" >'UTF-8'</span>,
    <span class="string" >'version'</span> =&gt; SOAP_1_2,
    <span class="string" >'uri'</span> =&gt; <span class="string" >'urn:HogeNamespace'</span>,
    <span class="string" >'actor'</span> =&gt; <span class="string" >'urn:HogeNameSpace'</span>,
    <span class="string" >'classmap'</span> =&gt; <span class="keyword" >array</span>(
        <span class="string" >'keyElement'</span> =&gt; <span class="string" >'MyKeyEntity'</span>
    )
));
</pre>

<pre class="php">&lt;?xml version=<span class="string" >"1.0"</span> encoding=<span class="string" >"UTF-8"</span>?&gt;
&lt;SOAP-ENV:Envelope xmlns:SOAP-ENV=<span class="string" >"http://schemas.xmlsoap.org/soap/envelope/"</span> xmlns:ns1=<span class="string" >"urn:HogeNamespace"</span>&gt;
    &lt;SOAP-ENV:Body&gt;
        &lt;SOAP-ENV:add&gt;
            &lt;keyElement xsi:type=<span class="string" >"ns1:keyElement"</span>&gt;
                &lt;user&gt;nowel&lt;/user&gt;
                &lt;key&gt;dhaTXKD&lt;/key&gt;
                &lt;timestamp&gt;1186410253&lt;/timestamp&gt;
            &lt;/keyElement&gt;
        &lt;/SOAP-ENV:add&gt;
    &lt;/SOAP-ENV:Body&gt;
&lt;/SOAP-ENV:Envelope&gt;</pre>

<p>最後にWSDLを書きますが、targetNamespaceが一致しないとうまく変換してくれないです。</p>

<h3>typemapオプション</h3>
<p>typemapオプションはclassmapと似ていますが、どうやらclassmapのように完全指定の名空間以外について変換できるロジックを委譲してくれる仕組っぽいです(あまり調べてない)<br />
次のrequestについて、typemapオプションを使った例を書きます。</p>

<pre class="php">
<span class="vars" >$server</span> = <span class="keyword" >new</span> SoapServer(<span class="vars" >$wsdl</span>, <span class="keyword" >array</span>(
    <span class="string" >'encoding'</span> =&gt; <span class="string" >'UTF-8'</span>,
    <span class="string" >'version'</span> =&gt; SOAP_1_2,
    <span class="string" >'uri'</span> =&gt; <span class="string" >'urn:HogeNamespace'</span>,
    <span class="string" >'actor'</span> =&gt; <span class="string" >'urn:HogeNameSpace'</span>,
    <span class="string" >'typemap'</span> =&gt; <span class="keyword" >array</span>(
        <span class="keyword" >array</span>(
            <span class="string" >'type_ns'</span> =&gt; <span class="string" >'urn:HogeNameSpace'</span>,
            <span class="string" >'type_name'</span> =&gt; <span class="string" >'keyElement'</span>,
            <span class="string" >'from_xml'</span>  =&gt; <span class="keyword" >array</span>(<span class="string" >'SoapTypeUtils'</span>, <span class="string" >'toKeyElement'</span>)
        )
    )
));

<span class="keyword" >class</span> MyKeyEntity {
    <span class="keyword" >public</span> <span class="vars" >$user</span>;
    <span class="keyword" >public</span> <span class="vars" >$key</span>;
    <span class="keyword" >public</span> <span class="vars" >$timestamp</span>;
}

<span class="keyword" >class</span> SoapTypeUtils {
    <span class="keyword" >public</span> <span class="keyword" >static</span> <span class="keyword" >function</span> toKeyElement(<span class="vars" >$xml</span>){
        <span class="vars" >$std</span> = simplexml_load_string(<span class="vars" >$xml</span>);
        <span class="vars" >$key</span> = <span class="keyword" >new</span> MyKeyEntity;
        <span class="vars" >$key</span>-&gt;user = (string) <span class="vars" >$std</span>-&gt;user;
        <span class="vars" >$key</span>-&gt;key = (string) <span class="vars" >$std</span>-&gt;key;
        <span class="vars" >$key</span>-&gt;timestamp = (double) <span class="vars" >$std</span>-&gt;timestamp;
        <span class="keyword" >return</span> <span class="vars" >$key</span>;
    }
}</pre>

<pre class="php">&lt;?xml version=<span class="string" >"1.0"</span> encoding=<span class="string" >"UTF-8"</span>?&gt;
&lt;SOAP-ENV:Envelope xmlns:SOAP-ENV=<span class="string" >"http://schemas.xmlsoap.org/soap/envelope/"</span>&gt;
    &lt;SOAP-ENV:Body&gt;
        &lt;SOAP-ENV:add&gt;
            &lt;keyElement&gt;
                &lt;user&gt;nowel&lt;/user&gt;
                &lt;key&gt;j6SZmtu&lt;/key&gt;
                &lt;timestamp&gt;1186413799&lt;/timestamp&gt;
            &lt;/keyElement&gt;
        &lt;/SOAP-ENV:add&gt;
    &lt;/SOAP-ENV:Body&gt;
&lt;/SOAP-ENV:Envelope&gt;</pre>

<p>typemapオプションは複数の<code>xsd:complexType</code>について、変換ロジックを埋め込めるので、ちょっと便利です。<br />
また、名空間が複数存在する場合に使えそうです。</p>

<h3>classmapとtypemapに関する注意点</h3>
<p>以下のWSDLの場合に、名空間に注意が必要です。注意というか<a href="http://jp2.php.net/manual/ja/function.soap-soapclient-gettypes.php">SoapClient::__getTypes</a>に表示されるように正しくwsdlの記述をしないと、classmapとtypemapの変換をおこなってくれません。</p>

<pre class="php">&lt;?xml version=<span class="string" >"1.0"</span> encoding=<span class="string" >"UTF-8"</span>?&gt;
&lt;definitions name=<span class="string" >"$name"</span>
             targetNamespace=<span class="string" >"$namespace"</span>
             xmlns:impl=<span class="string" >"$namespace"</span>
             xmlns:xsd=<span class="string" >"http://www.w3.org/2001/XMLSchema"</span>
             xmlns:soap=<span class="string" >"http://schemas.xmlsoap.org/wsdl/soap/"</span>
             xmlns:soapenc=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span>
             xmlns:mime=<span class="string" >"http://schemas.xmlsoap.org/wsdl/mime/"</span>
             xmlns:wsdl=<span class="string" >"http://schemas.xmlsoap.org/wsdl/"</span>
             xmlns=<span class="string" >"http://schemas.xmlsoap.org/wsdl/"</span>&gt;
    &lt;types&gt;
	    &lt;xsd:schema targetNamespace=<span class="string" >"$namespace"</span>&gt;
	        &lt;xsd:complexType name=<span class="string" >"keyElement"</span>&gt;
	            &lt;xsd:all&gt;
	                &lt;xsd:element name=<span class="string" >"user"</span> nillable=<span class="string" >"false"</span> type=<span class="string" >"xsd:string"</span>/&gt;
	                &lt;xsd:element name=<span class="string" >"key"</span> nillable=<span class="string" >"false"</span> type=<span class="string" >"xsd:string"</span>/&gt;
	                &lt;xsd:element name=<span class="string" >"timestamp"</span> nillable=<span class="string" >"false"</span> type=<span class="string" >"xsd:long"</span>/&gt;
	            &lt;/xsd:all&gt;
	        &lt;/xsd:complexType&gt;
	        &lt;xsd:complexType name=<span class="string" >"RemoteAuthenticationException"</span> /&gt;
	        &lt;xsd:complexType name=<span class="string" >"ValidateException"</span> /&gt;
	        &lt;xsd:complexType name=<span class="string" >"RemoteException"</span> /&gt;
	    &lt;/xsd:schema&gt;
    &lt;/types&gt;
    &lt;message name=<span class="string" >"addRequest"</span>&gt;
        &lt;part name=<span class="string" >"keyElement"</span> type=<span class="string" >"impl:keyElement"</span> /&gt;
    &lt;/message&gt;
    &lt;message name=<span class="string" >"addResponse"</span>&gt;
        &lt;part name=<span class="string" >"return"</span> type=<span class="string" >"xsd:boolean"</span> /&gt;
    &lt;/message&gt;
    &lt;message name=<span class="string" >"RemoteAuthenticationException"</span>&gt;
        &lt;part name=<span class="string" >"fault"</span> type=<span class="string" >"impl:RemoteAuthenticationException"</span> /&gt;
    &lt;/message&gt;
    &lt;message name=<span class="string" >"RemoteException"</span>&gt;
        &lt;part name=<span class="string" >"fault"</span> type=<span class="string" >"impl:RemoteException"</span> /&gt;
    &lt;/message&gt;
    &lt;message name=<span class="string" >"ValidateException"</span>&gt;
        &lt;part name=<span class="string" >"fault"</span> type=<span class="string" >"impl:ValidateException"</span> /&gt;
    &lt;/message&gt;
    &lt;portType name=<span class="string" >"$portType"</span>&gt;
        &lt;operation name=<span class="string" >"add"</span>&gt;
            &lt;input message=<span class="string" >"impl:addRequest"</span> name=<span class="string" >"addRequest"</span> /&gt;
            &lt;output message=<span class="string" >"impl:addResponse"</span> name=<span class="string" >"addResponse"</span> /&gt;
            &lt;fault message=<span class="string" >"impl:ValidateException"</span> name=<span class="string" >"ValidateException"</span>/&gt;
            &lt;fault message=<span class="string" >"impl:RemoteException"</span> name=<span class="string" >"RemoteException"</span>/&gt;
        &lt;/operation&gt;
    &lt;/portType&gt;
    &lt;binding name=<span class="string" >"$binding"</span> type=<span class="string" >"impl:$portType"</span>&gt;
        &lt;soap:binding style=<span class="string" >"rpc"</span> transport=<span class="string" >"http://schemas.xmlsoap.org/soap/http"</span>/&gt;
        &lt;operation name=<span class="string" >"add"</span>&gt;
            &lt;soap:operation soapAction=<span class="string" >""</span>/&gt;
            &lt;input name=<span class="string" >"addRequest"</span>&gt;
                &lt;soap:body namespace=<span class="string" >"$namespace"</span> encodingStyle=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span> <span class="keyword" >use</span>=<span class="string" >"encoded"</span> /&gt;
            &lt;/input&gt;
            &lt;output name=<span class="string" >"addResponse"</span>&gt;
                &lt;soap:body namespace=<span class="string" >"$namespace"</span> encodingStyle=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span> <span class="keyword" >use</span>=<span class="string" >"encoded"</span> /&gt;
            &lt;/output&gt;
            &lt;fault name=<span class="string" >"RemoteAuthenticationException"</span>&gt;
                &lt;soap:fault namespace=<span class="string" >"$namespace"</span> encodingStyle=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span> <span class="keyword" >use</span>=<span class="string" >"encoded"</span> /&gt;
            &lt;/fault&gt;
            &lt;fault name=<span class="string" >"RemoteException"</span>&gt;
                &lt;soap:fault namespace=<span class="string" >"$namespace"</span> encodingStyle=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span> <span class="keyword" >use</span>=<span class="string" >"encoded"</span> /&gt;
            &lt;/fault&gt;
            &lt;fault name=<span class="string" >"ValidateException"</span>&gt;
                &lt;soap:fault namespace=<span class="string" >"$namespace"</span> encodingStyle=<span class="string" >"http://schemas.xmlsoap.org/soap/encoding/"</span> <span class="keyword" >use</span>=<span class="string" >"encoded"</span> /&gt;
            &lt;/fault&gt;
        &lt;/operation&gt;
    &lt;/binding&gt;
    &lt;service name=<span class="string" >"$service"</span>&gt;
        &lt;port binding=<span class="string" >"impl:$binding"</span> name=<span class="string" >"$port"</span>&gt;
            &lt;soap:address location=<span class="string" >"$location"</span>/&gt;
        &lt;/port&gt;
    &lt;/service&gt;
&lt;/definitions&gt;
</pre>

<p>やはり、重要なのは、<code>xsd:schema targetNamespace</code>と<code>&lt;part name=<span class="string" >"keyElement"</span> type=<span class="string" >"impl:keyElement"</span> /&gt;</code>の部分ですかね。<br />
ちょっとサンプルとは違うns使ってますけど。</p>

<p>また、このWSDLをSoapClientで読み込んで、<code>__getTypes()</code>をすると次のようなtypesが定義されます。(classmapやtypemapはこれにマッピングするのですね)</p>

<pre class="php">  keyElement login(string $user, string $password)
  <span class="keyword" >boolean</span> add(keyElement $keyElement)
  <span class="keyword" >boolean</span> update(keyElement $keyElement)
  <span class="keyword" >boolean</span> remove(keyElement $keyElement)
  <span class="keyword" >boolean</span> logout(keyElement $keyElement)

  struct keyElement {
     string user;
     string key;
     <span class="keyword" >long</span> timestamp;
  }
  struct RemoteAuthenticationException {}
  struct ValidateException {}
  struct RemoteException {}
</pre>

<h3>おまけ、SoapServer::setObjectメソッド</h3>
<p>ドキュメントに書かれてなかったですが、<code>php-version/ext/soap/tests</code>でsetObjectなるメソッドがあったので、使ってみると、これが非常に便利でした。<br />
既存の<a href="http://jp2.php.net/manual/ja/function.soap-soapserver-setclass.php">SoapServer::setClass</a>はクラスを指定した上で、コンストラクタパラメータの指定しかできなかったですが、<code>setOject</code>は既に、生成済みのオブジェクトについてマッピングを行ってくれます。<br />
素晴らしいです。</p>

<p>たとえば、こんなクラスをPOPOで用意する(POPOじゃないけど ^^;)</p>

<pre class="php">
<span class="keyword" >class</span> HogeService <span class="keyword" >implements</span> IService {

    <span class="keyword" >private</span> <span class="vars" >$hogeDao</span>;

    <span class="keyword" >public</span> <span class="keyword" >function</span> setHogeDao(HogeDao <span class="vars" >$dao</span>){
        <span class="vars" >$this</span>-&gt;hogeDao = <span class="vars" >$dao</span>;
    }

    <span class="keyword" >public</span> <span class="keyword" >function</span> login(<span class="vars" >$user</span>, <span class="vars" >$password</span>){
        <span class="comment" >// do something</span>
    }

    <span class="keyword" >public</span> <span class="keyword" >function</span> add(MyKeyElement <span class="vars" >$element</span>){
        <span class="vars" >$this</span>-&gt;hogeDao-&gt;insert(<span class="vars" >$element</span>);
    }

    <span class="keyword" >public</span> <span class="keyword" >function</span> logout(<span class="vars" >$user</span>){
        <span class="comment" >// do something</span>
    }
}
</pre>

<p>あとは、S2Containerで管理する</p>

<pre class="php">&lt;component <span class="keyword" >class</span>=<span class="string" >"HogeService"</span>&gt;
    &lt;property name=<span class="string" >"hogeDao"</span>&gt;
        &lt;component <span class="keyword" >class</span>=<span class="string" >"HogeDao"</span>&gt;
            &lt;aspect&gt;dao.interceptor&lt;/aspect&gt;
        &lt;/component&gt;
    &lt;/property&gt;
&lt;/component&gt;</pre>

<p>んで、どっかにinjectするもよし、コンテナから取り出してもよし。SoapServer::setObjectするだけで、Soapのマッピングを行ってくださる。</p>

<pre class="php">S2Container_SingletonS2ContainerFactory::init();
<span class="vars" >$container</span> = S2Container_SingletonS2ContainerFactory::getContainer();
<span class="vars" >$service</span> = <span class="vars" >$container</span>-&gt;getComponent(<span class="string" >'HogeService'</span>);

<span class="vars" >$server</span> = <span class="keyword" >new</span> SoapServer(...);
<span class="vars" >$server</span>-&gt;setObject(<span class="vars" >$service</span>);
<span class="vars" >$server</span>-&gt;handle();
</pre>

<p>SoapServerは何気に便利なのでした。</p>
]]>
</content:encoded>
</item>

</rdf:RDF>