如何填充复合消息并作为SoapServer响应XML返回?

时间:2012-10-07 19:51:34

标签: php xml web-services soap soapserver

我正在设置一个SOAP Web服务,它应该返回一个复合消息 此消息的有效实例如下:

<dl190Response xmlns="http://pse/">
    <cdhead cisprik="5563167"/>
    <mvts>
        <mvts_S att="a1">
            <x>x1</x>
            <w>w1</w>
        </mvts_S>
        <mvts_S>
            <x>x2</x>
            <w>w2</w>
        </mvts_S>
    </mvts>
</dl190Response>

所有这些都在wsdl:

中得到了很好的定义
<?xml version="1.0" encoding="UTF-8"?>
<definitions
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://pse/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    name="PSE"
    targetNamespace="http://pse/">
    <types>
        <xs:schema xmlns="http://pse/" targetNamespace="http://pse/">
            <xs:complexType name="cdhead_T">
                <xs:attribute name="cisprik" type="xs:long"/>
            </xs:complexType>
            <xs:complexType name="mvts_T">
                <xs:sequence>
                    <xs:element name="mvts_S" type="mvts_S_T" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="mvts_S_T">
                <xs:sequence>
                    <xs:element name="x" type="xs:string"/>
                    <xs:element name="w" type="xs:string"/>
                </xs:sequence>
                <xs:attribute name="att" type="xs:string" use="optional"/>
            </xs:complexType>
        </xs:schema>
    </types>
    <message name="DL190Req">
        <part name="cdhead" type="tns:cdhead_T"/>
    </message>
    <message name="DL190Res">
        <part name="cdhead" type="tns:cdhead_T"/>
        <part name="mvts" type="tns:mvts_T"/>
    </message>
    <portType name="DLPortType">
        <operation name="dl190">
            <input message="tns:DL190Req"/>
            <output message="tns:DL190Res"/>
        </operation>
    </portType>
    <binding name="DLBinding" type="tns:DLPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="dl190">
            <soap:operation soapAction="http://www.testServer.com/test_soap.php#dl190"/>
            <input>
                <soap:body use="literal" namespace="http://pse/"/>
            </input>
            <output>
                <soap:body use="literal" namespace="http://pse/"/>
            </output>
        </operation>
    </binding>
    <service name="PSE">
        <port name="DLPortType" binding="tns:DLBinding">
            <soap:address location="http://www.testServer.com/test_soap.php"/>
        </port>
    </service>
</definitions>

我一直在服务器端test_soap.php无休止地工作以使其正确,但我没有成功。 在返回XML之前正常工作的部分原因如下:

<?php
    class PSE {
        function dl190 ($arg) {
            //I don't need to extract the input data just now

            mysql_connect('127.0.0.1:3306', 'user', 'password');
            mysql_select_db('myDatabase');

            $xml = new SimpleXMLElement('<dl190Res/>');
            $xml -> addChild('cdhead');
            $mvts = $xml -> addChild('mvts');

            $rows = mysql_query('select * from trx');
            while($data = mysql_fetch_assoc($rows)) {
                $mvts_S = $mvts -> addChild('mvts_S'); 
                foreach($data as $key => $value) {
                    if ($key == 'att') { $mvts_S -> addAttribute($key, $value);}
                    else    {$mvts_S -> addChild($key, $value);}
                }
            };

            $dom = dom_import_simplexml ($xml) -> ownerDocument;

            // now respond to the request and return the XML
        }

    };
    ini_set( "soap.wsdl_cache_enabled", "0");
    $server = new SoapServer ("test.wsdl");
    $server -> setClass ('PSE');
    $server -> setObject (new PSE());
    $server -> handle();    
?>

我尝试了几乎所有我能想到的答案,但我没有成功。我能够对之前只包含一部分的消息做同样的事情(参见我最近的问题+答案) 但在这里,有两个消息部分,我没有成功。

$ xml内容的调试显示,当肥皂服务器将其包装到Envelope + Body中时,它正是我希望看到的内容。

实际上情况与只有一个消息部分的情况不同:只要我首先剥离XML声明,我就可以从一个部分创建一个新的SoapVar,并返回它。在这里我不能这样做,因为返回值由两部分组成。

所以我想知道我现在应该做以下哪些事情:

  1. 为响应消息声明一个类并填充并返回
  2. 使用SoapVar和/或SoapParam执行一些魔法(请注意,我已经尝试了很多)
  3. 使用数组和SoapVar执行一些魔法(已经尝试了很多)
  4. 以某种方式(如何?)向wsdl寻求帮助
  5. 完全不同的东西
  6. 使用SoapServer退出整个噩梦并从头开始创建自己的http响应
  7. 我很感谢所有这方面的帮助,所以你们所有肥皂专家都会毫不犹豫地试着回答这个问题!

    ADDITION

    作为临时解决方法,我编辑了WSDL,将响应消息更改为只有一个部分。这允许我传递预期的消息作为预期的两个部分的连接(或者任何其他消息,因为SoapVar没有对返回的值执行消息定义的结构WSDL检查):

    $xml1 = new SimpleXMLElement('<cdhead/>');
    $xml1 -> addAttribute ('xmlns', 'http://pse/');
    $xml1 -> addAttribute ('cisprik', $newCisprik);
    
    $xml2 = new SimpleXMLElement('<mvts/>');
    
    $rows = mysql_query('select * from trx');
    while($data = mysql_fetch_assoc($rows)) {
        $mvts_S = $xml2 -> addChild('mvts_S'); 
        foreach($data as $key => $value) {
            if ($key == 'att') { $mvts_S -> addAttribute($key, $value);}
            else    {$mvts_S -> addChild($key, $value);}
        }
    };
    
    $dom1 = dom_import_simplexml ($xml1) -> ownerDocument;
    $dom2 = dom_import_simplexml ($xml2) -> ownerDocument;
    $part1 = $dom1 -> saveXML($dom1 -> documentElement);
    $part2 = $dom2 -> saveXML($dom2 -> documentElement);
    
    $result = new SoapVar ($part1 . $part2, XSD_ANYXML);
    

    特殊的是,串联当然不是有效的XML,缺少周围的根元素,但无论如何SoapVar都能解析它。

    所以它是:任何在SoapVar和SoapParam / SoapServer中有详细见解的人都可以解释是否可以返回两个消息部分?
    并解释如何做到这一点?
    或者,或者,在其他SOAP设置中提供有关如何执行此操作的详细信息?

2 个答案:

答案 0 :(得分:6)

我尝试并设置了你的最小SoapServer,这就是我所做的:

  1. 将您的wsdl和PHP脚本复制到一个文件夹。
  2. 将wsdl位置引用更改为指向php脚本。
  3. 将WSDL导入SoapUI - 强烈推荐它,它是免费的!
  4. 试图致电该服务。
  5. 这是我的通话请求:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pse="http://pse/">
       <soapenv:Header/>
       <soapenv:Body>
          <pse:dl190>
             <cdhead cisprik="0"/>
          </pse:dl190>
       </soapenv:Body>
    </soapenv:Envelope>
    

    由于你对数据库的调用,它最初没有用,但我知道你真的只需要一个如何在soap层上正确响应的解决方案,你会弄明白其余的。

    这是一个简单的解决方案:

    <?php
    class PSE
    {
        public function dl190($arg)
        {
            //var_dump($arg) is:
            //object(stdClass)#3 (1) {
            //   ["cisprik"]=> int(0)
            //}
    
            $fakeResult = array();
    
            $fakeResult[0] = new stdClass();
            $fakeResult[0]->cisprik = 23;
    
            $fakeResult[1] = array();
    
            $fakeResult[1][0] = new stdClass();
            $fakeResult[1][0]->att = "a1";
            $fakeResult[1][0]->x = "x1";
            $fakeResult[1][0]->w = "w1";
    
            $fakeResult[1][1] = new stdClass();
            //$fakeResult[1][1]->att = "a1";
            $fakeResult[1][1]->x = "x2";
            $fakeResult[1][1]->w = "w2";
    
            return $fakeResult;
        }
    }
    
    //ini_set("soap.wsdl_cache_enabled", "0");
    
    $server = new SoapServer ("wsdl.xml");
    $server->setObject(new PSE());
    $server->handle();
    

    请注意,PHP基本上只在请求参数中发出stdClass和数组的混合(我在顶部转储了你得到的注释)。这是一件令人伤心的事情,但我认为在同一级别做出回应是不公平的事情,而不是通过使用XML来改变事态。

    如果针对此代码执行上述请求,您将收到此soap响应:

    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://pse/">
       <SOAP-ENV:Body>
          <ns1:dl190Response>
             <cdhead cisprik="23"/>
             <mvts>
                <mvts_S att="a1">
                   <x>x1</x>
                   <w>w1</w>
                </mvts_S>
                <mvts_S>
                   <x>x2</x>
                   <w>w2</w>
                </mvts_S>
             </mvts>
          </ns1:dl190Response>
       </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    

    但仍有改进的余地。 PHP SoapServer(以及SoapClient)也有一个名为classmap的功能,我强烈建议您使用它。如果您的IDE支持任何类型的PHPDoc自动完成,您几乎可以在任何处理正确设置值的地方利用它。

    这是我的带有类图定义的版本。请注意,我用“PSE”将它们全部加上前缀,以突出显示类名不需要在WSDL中的complexTypes之后命名。

    <?php
    class PSE
    {
        public function dl190(PSE_cdhead_T $arg)
        {
            //  var_dump($arg) is:
            //  object(PSE_cdhead_T)#3 (1) {
            //      ["cisprik"]=> int(0)
            //  }
    
            $fakeResult = array();
            $fakeResult[0] = new PSE_cdhead_T();
            $fakeResult[0]->cisprik = 23;
    
            $fakeResult[1] = array();
    
            $fakeResult[1][0] = new PSE_mvts_S_T();
            $fakeResult[1][0]->att = "a1";
            $fakeResult[1][0]->x = "x1";
            $fakeResult[1][0]->w = "w1";
    
            $fakeResult[1][1] = new PSE_mvts_S_T();
            //$fakeResult[1][1]->att = "a1";
            $fakeResult[1][1]->x = "x2";
            $fakeResult[1][1]->w = "w2";
    
            return $fakeResult;
        }
    }
    
    class PSE_cdhead_T {
        /**
         * @var int
         */
        public $cisprik;
    }
    
    
    class PSE_mvts_S_T {
        /**
         * @var string
         */
        public $att;
    
        /**
         * @var string
         */
        public $x;
    
        /**
         * @var string
         */
        public $w;
    }
    
    //ini_set("soap.wsdl_cache_enabled", "0");
    
    $classmap = array(
        'cdhead_T' => 'PSE_cdhead_T',
        'mvts_S_T' => 'PSE_mvts_S_T',
    );
    
    $serverOptions = array(
        'encoding' => 'utf-8',
        'classmap' => $classmap,
        'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
    );
    
    $server = new SoapServer ("wsdl.xml", $serverOptions);
    
    $server->setObject(new PSE());
    $server->handle();
    

    不幸的是,一个烦人的问题没有得到解决:在你的响应中,你不能使用一个类,但是必须使用一个数组,而没有任何提示将哪个索引参数映射到哪个xml结果。这真的很糟糕,但要改变它,你将不得不改变WSDL。

    我不开心向大家报告,我在创建WSDL文件没有专家。我尝试添加复杂类型作为响应中的唯一元素。如果你看看我的第二个版本中的转储,你会看到你得到一个类PSE_cdhead_T,它是请求消息中唯一部分的映射complexType。

    因为响应消息有两部分,所以SoapServer必须将它们放在一个数组中。没有可能的命名引用。我建议你在这里添加一个新的complexType,并在地图中相应地创建一个新类,如下所示:

    class PSE_DL190_Response
    {
        /**
         * @var PSE_cdhead_T
         */
        public $cdhead;
        /**
         * @var PSE_mvts_S_T[]
         */
        public $mvts;
    }
    

    然后您可以更轻松地准备响应:

    $fakeResult = new PSE_DL190_Response();
    $fakeResult->cdhead = new PSE_cdhead_T(); // Set the one cdhead structure
    $fakeResult->mvts[] = new PSE_mvts_S_T(); // Add one mvts structure;
    

    这很可能会导致您对XML响应的更改 - 但我无法评估其影响。

    最后一个想法:有一些用于PHP的WSDL代码生成器,您可以尝试。他们将自动生成类图所需的类。上次我尝试它们时,它们似乎工作,但不是我测试的所有WSDL文件。 Soap定义似乎过于复杂,无法做到这一点。但如果它有效,那么它是值得的,而不是手动创建它们。

答案 1 :(得分:1)

我不是SOAP专家,但是必须在一些项目上使用SOAP来与第三方服务器连接是一场噩梦,部分原因在于不太好的服务器实现和我自己的无知作为一个菜鸟。但是我记得在尝试使用PHP SOAP类时遇到很多问题然后我转而使用NuSOAP toolkit并且更容易完成任务并解决了我遇到的许多奇怪问题。所以我的建议是使用像NuSOAP这样的工具包,看看事情是否更有意义。

SOAP is an old spec这并不错,但我认为它不再适用(WG closed 2009-07-10)而且它太脏了,使用起来很痛苦。 Microsoft SOAP toolkit甚至已被弃用和退役。所以,如果你能走另一条路,那就去做吧。

或许可以去RESTful路线。

  

REST通过允许不同服务之间的松散耦合来促进Web服务器之间的事务。 REST的类型不如其对应的SOAP强。 REST语言基于名词和动词的使用,并强调可读性。与SOAP不同,REST不需要XML解析,也不需要与服务提供者之间的消息头。这最终使用较少的带宽。 REST错误处理也与SOAP使用的不同。