Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The State of SOAP in PHP (ZendCon2010 2010-11-02)

The State of SOAP in PHP (ZendCon2010 2010-11-02)

David Zuelke

November 02, 2010
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. SOAP TRANSPORTS • Transports are used for message transmission •

    Most important ones: • HTTP/HTTPS • SMTP
  2. MESSAGES • Wrapped in <Envelope> • <Header>s and a <Body>

    • Structure is identical for Request and Response <?xml  version="1.0"  encoding="UTF-­‐8"?> <SOAP-­‐ENV:Envelope    xmlns:SOAP-­‐ENV="http:// schemas.xmlsoap.org/soap/envelope/"    xmlns:ns1="http://agavi.org/ sampleapp" >    <SOAP-­‐ENV:Body>        <ns1:getProductResponse>            <product>                <id>123456</id>                <name>Red  Stapler</name>                <price>3.14</price>            </product>        </ns1:getProductResponse>    </SOAP-­‐ENV:Body> </SOAP-­‐ENV:Envelope>
  3. <?xml  version="1.0"  encoding="utf-­‐8"?> <wsdl:definitions  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"   xmlns:xsd="http://www.w3.org/2001/XMLSchema"  xmlns:soap="http:// schemas.xmlsoap.org/wsdl/soap/"  xmlns:soapenc="http://schemas.xmlsoap.org/soap/

    encoding/"  xmlns="http://schemas.xmlsoap.org/wsdl/"  xmlns:tns="http://agavi.org/ sampleapp/types"  xmlns:asa="http://agavi.org/sampleapp"   name="AgaviSampleApplication"  targetNamespace="http://agavi.org/sampleapp">    <wsdl:types>        <xsd:schema  xmlns:soap-­‐enc="http://schemas.xmlsoap.org/soap/encoding/"   targetNamespace="http://agavi.org/sampleapp/types">            <xsd:complexType  name="Product">                <xsd:sequence>                    <xsd:element  name="id"  type="xsd:int"/>                    <xsd:element  name="name"  type="xsd:string"/>                    <xsd:element  name="price"  type="xsd:float"/>                </xsd:sequence>            </xsd:complexType>            <xsd:complexType  name="ArrayOfProducts">                <xsd:complexContent>                    <xsd:extension  base="soap-­‐enc:Array">                        <xsd:attribute  ref="soap-­‐enc:arrayType"  wsdl:arrayType="tns:Product []"/>                    </xsd:extension>                </xsd:complexContent>            </xsd:complexType>        </xsd:schema>    </wsdl:types>    <wsdl:portType  name="AgaviSampleApplicationPortType">        <wsdl:operation  name="getProduct">            <wsdl:input  message="asa:getProductRequest"/>            <wsdl:output  message="asa:getProductResponse"/>
  4. $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'  =>  true,  

     'trace'  =>  true, )); try  {    var_dump($client-­‐>listProducts()); }  catch(SoapFault  $e)  {    //  here  be  dragons } array    0  =>          object(stdClass)[3]            public  'id'  =>  int  8172401            public  'name'  =>  string  'TPS  Report  Cover  Sheet'  (length=22)            public  'price'  =>  float  0.89    1  =>          object(stdClass)[4]            public  'id'  =>  int  917246            public  'name'  =>  string  'Weighted  Companion  Cube'  (length=23)            public  'price'  =>  float  129.99
  5. BASICS $client  =  new  SoapClient(    'http://acme.com/product.wsdl',  //  URL  to

     WSDL  describing  the  service    array(  //  array  of  additional  options  for  the  client        'exceptions'  =>  true,  //  throw  SoapFault  exceptions  on  errors        'trace'  =>  true,  //  allow  use  of  SoapClient::__getLast…()    ) ); $allProducts  =  $client-­‐>listProducts();  //  $allProducts  contains  return  value
  6. GETTING AVAILABLE FUNCS $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'

     =>  true,    'trace'  =>  true, )); var_dump($client-­‐>__getFunctions()); array    0  =>  string  'Product  getProduct(int  $id)'  (length=27)    1  =>  string  'ArrayOfProducts  listProducts()'  (length=30)
  7. GETTING AVAILABLE TYPES $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'

     =>  true,    'trace'  =>  true, )); var_dump($client-­‐>__getTypes()); array    0  =>  string  'struct  Product  {  int  id;  string  name;  float  price; }'  (length=55)    1  =>  string  'Product  ArrayOfProducts[]'  (length=25)
  8. FAULT HANDLING $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'  =>

     true,    'trace'  =>  true, )); try  {    $newThing  =  $client-­‐>createProduct(new  stdClass()); }  catch(SoapFault  $e)  {    //  could  be  a  client-­‐side  fault  e.g.  if  fields  are  missing    //  or  a  server-­‐side  fault  if  the  server  had  any  objections  :) }
  9. SOAP HEADERS $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'  =>

     true,    'trace'  =>  true, )); $client-­‐>setSoapHeader(    new  SoapHeader('http://acme.com/soap/products',  'username',  'Chuck  Norris') ); $client-­‐>setSoapHeader(    new  SoapHeader('http://acme.com/soap/products',  'password',  'r0undh0usek!ck') ); //  headers  will  be  sent  along  with  the  request $allProducts  =  $client-­‐>listProducts();
  10. CLASSMAPS class  Product  {    protected  $id,  $name,  $price;  

     //  imagine  getters  and  setters  here } $client  =  new  SoapClient('http://acme.com/product.wsdl',  array(    'exceptions'  =>  true,    'trace'  =>  true,    'classmap'  =>  array(        'Product'  =>  'Product',  //  no  XML  namespace  here,  which  can  be  problematic…    ), )); var_dump($client-­‐>getProduct(123456)); object(Product)[2]    protected  'id'  =>  int  123456    protected  'name'  =>  string  'Red  Stapler'  (length=11)    protected  'price'  =>  float  3.14
  11. TYPEMAPS • Used for custom serialization and unserialization in rare

    cases • Example: • xsd:long is mapped to PHP int, overflows on 32bit archs • Needs two callbacks: • one for XML->PHP conversion • one for PHP->XML conversion
  12. TYPEMAP EXAMPLE: XS:LONG function  to_long_xml($longVal)  {    return  '<long>'  .

     $longVal  .  '</long>'; } function  from_long_xml($xmlFragmentString)  {    return  (string)strip_tags($xmlFragmentString); } $client  =  new  SoapClient('http://acme.com/products.wsdl',  array(    'typemap'  =>  array(        array(            'type_ns'  =>  'http://www.w3.org/2001/XMLSchema',            'type_name'  =>  'long',            'to_xml'  =>  'to_long_xml',            'from_xml'  =>  'from_long_xml',        ),    ), )); you can use any name for this wrapper tag
  13. class  ProductService  {    public  function  getProduct($id)  {    

       //  witchcraft  here        return  $product;    }    public  function  listProducts()  {        //  more  witchcraft  here        return  $products;    } } $server  =  new  SoapServer('/path/to/local/products.wsdl',  array(/*  options…  */)); //  register  a  class  to  instantiate  that  has  all  the  methods $server-­‐>setClass('ProductService'); //  alternative:  use  an  existing  instance $server-­‐>setObject(new  ProductService()); //  rock  and  roll $server-­‐>handle(); A BASIC SERVER
  14. class  ProductService  {    public  function  getProduct($id)  {    

       //  witchcraft  here        return  $product;    }    public  function  listProducts()  {        //  more  witchcraft  here        return  $products;    }    public  function  username($value)  {        //  check  if  it's  really  chuck  norris    }    public  function  password($value)  {        //  check  if  he  did  a  roundhouse  kick    } } $server  =  new  SoapServer('/path/to/local/products.wsdl',  array(/*  options…  */)); //  register  a  class  to  instantiate  that  has  all  the  methods $server-­‐>setClass('ProductService'); //  rock  and  roll $server-­‐>handle(); DEALING WITH HEADERS
  15. class  ProductService  {    public  function  getProduct($id)  {    

       if($product  =  ProductFinder::retrieveById($id))  {            return  $product;        }  else  {            return  new  SoapFault('Server',  'No  such  product');        }    }    public  function  listProducts()  {        //  more  witchcraft  here        return  $products;    } } PRODUCING FAULTS
  16. class  ProductService  {    public  function  getTwoThings()  {    

       //  rocket  science  here        return  array($product1,  $product2);    } } MULTI-PART RETURN VALUES <wsdl:message  name="getTwoThingsRequest">    <wsdl:part  name="id1"  type="xsd:int"/>    <wsdl:part  name="id2"  type="xsd:int"/> </wsdl:message> <wsdl:message  name="getTwoThingsResponse">    <wsdl:part  name="firstThing"  type="tns:Product"/>    <wsdl:part  name="secondThing"  type="tns:Product"/> </wsdl:message> list($p1,  $p2)  =  $client-­‐>getTwoThings($id1,  $id2));
  17. <complexType  name="ArrayOfProducts">    <element  name="products"  type="foo:Product"  maxOccurs="unbounded"  /> </complexType> XML

    SCHEMA ARRAYS <xsd:complexType  name="ArrayOfProducts">    <xsd:complexContent>        <xsd:extension  base="soap-­‐enc:Array">            <xsd:attribute  ref="soap-­‐enc:arrayType"  wsdl:arrayType="tns:Product[]"/>        </xsd:extension>    </xsd:complexContent> </xsd:complexType> =
  18. HASHMAPS (W/ STRING KEYS) <xsd:schema    xmlns:apache-­‐enc="http:// xml.apache.org/xml-­‐soap">    <xsd:complexType

     name="MyHashmap">        <xsd:element            name="parameters"            type="apache-­‐enc:Map"  />    </xsd:complexType> </xsd:schema> • Custom Apache Axis encoding style • be aware that it won’t offer good interoperability • Use any type for values, scalars for keys • Keys and values will use RPC encoding in the message
  19. ONE-WAY CALLS • SOAP Operations may have only a request,

    without a response declared • Both SoapClient and SoapServer will close the connection as soon as they can when calling such a method http://flic.kr/99996581@N00/1122331674/
  20. XSD:DATETIME TYPE MAP function  to_datetime_xml(DateTime  $dateTime)  {    return  '<dateTime>'.$dateTime-­‐>format('Y-­‐m-­‐d\TH:i:sP').'</dateTime>';

    } function  from_datetime_xml($xmlFragmentString)  {    return  new  DateTime(strip_tags($xmlFragmentString)); } $client  =  new  SoapClient('http://acme.com/products.wsdl',  array(    'typemap'  =>  array(        array(            'type_ns'  =>  'http://www.w3.org/2001/XMLSchema',            'type_name'  =>  'dateTime',            'to_xml'  =>  'to_datetime_xml',            'from_xml'  =>  'from_datetime_xml',        ),    ), ));
  21. DOCUMENT/LITERAL WRAPPED • Document style services yield messages that can

    be validated against the WSDL’s XML Schema • Unlike with RPC style services, such messages won’t contain the name of the called method anymore • Solution: wrap the message payload in an element that has the same name as the procedure you want to call • Not a problem in PHP, but you need to wrap/unwrap yourself
  22. ZEND FRAMEWORK • Zend_Soap_Client as a wrapper for SOAPClient •

    Zend_Soap_Server as a wrapper for SOAPServer • Zend_Soap_Wsdl for constructing WSDL documents • Zend_Soap_Autodiscover for auto WSDL generation
  23. class  AcmeProductService  {    /**      *  @param  

             int          The  ID  of  the  product.      *      *  @return          Product  The  product  object.      *      *  @deprecated  Call  Joe  from  sales  if  you  want  to  know  details  about  a  product…      */    public  function  getProduct($id)  {        //  witchcraft  goes  here        return  $product;    } } $autodiscover  =  new  Zend_Soap_AutoDiscover(); $autodiscover-­‐>setClass('AcmeProductService'); $autodiscover-­‐>handle();  //  only  dumps  a  WSDL,  does  not  start  a  server!
  24. AGAVI • Re-use existing Actions for SOAP Services • Needs

    some information about the service in WSDL format • WSDL auto-generated by the Routing • Requires basic knowledge of XML Schema and WSDL • Supports Document/Literal Wrapped for Servers