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

Sweet mother of SOAP @ PHPro Tech Meetup

Toon Verwerft
February 27, 2018

Sweet mother of SOAP @ PHPro Tech Meetup

SOAP ... Love it or hate it!

Many projects integrate with SOAP servers. In PHP, this can be implemented in manu different ways. The one way is more maintainable than the other.

In this talk I'll show you the most common errors you can make when implementing a SOAP service and I'll show you some of my worst experiences with SOAP. Finally, I'll show you how a decent SOAP integration is implemented in PHP.

Demo:
https://github.com/veewee/demo-soap-client

Package:
https://github.com/phpro/soap-client

Toon Verwerft

February 27, 2018
Tweet

More Decks by Toon Verwerft

Other Decks in Technology

Transcript

  1. • Chemical  composition • Household  used • Soap  making  process

    • Bathing  for  dummies Table  of  contents
  2. Simple  HTTP Documentation  – if  lucky Various  data  formats Faster

    Easier Standardized Protocol Service description XML Slower More complex
  3. Simple  HTTP Documentation  – if  lucky Various  data  formats Faster

    Easier Standardized Protocol Service description XML Slower More complex
  4. Simple  HTTP Documentation  – if  lucky Various  data  formats Faster

    Easier Standardized Protocol Service description XML Slower More complex
  5. Simple  HTTP Documentation  – if  lucky Various  data  formats Faster

    Easier Standardized Protocol Service description XML Slower More complex
  6. Simple  HTTP Documentation  – if  lucky Various  data  formats Faster

    Easier Standardized Protocol Service description XML Slower More complex
  7. <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-­‐envelope/" soap:encodingStyle="http://www.w3.org/2003/05/soap-­‐encoding"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-­‐open.org/wss/2004/01/oasis-­‐200401-­‐wss-­‐wssecurity-­‐secext-­‐1.0.xsd" soap:mustUnderstand="1"> <wsse:BinarySecurityToken xmlns:wsu="http://docs.oasis-­‐open.org/wss/2004/01/oasis-­‐200401-­‐wss-­‐wssecurity-­‐utility-­‐1.0.xsd" EncodingType="http://docs.oasis-­‐open.org/wss/2004/01/oasis-­‐200401-­‐wss-­‐soap-­‐message-­‐security-­‐1.0#Base64Binary"

    wsu:Id="pfx5ba7ce35-­‐b490-­‐f684-­‐a64b-­‐0dd2ffae07c1" ValueType="http://docs.oasis-­‐open.org/wss/2004/01/oasis-­‐200401-­‐wss-­‐x509-­‐token-­‐profile-­‐1.0#X509v3"/> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-­‐exc-­‐c14n#"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-­‐sha1"/> <ds:Reference URI="#pfxd3079e1b-­‐c37e-­‐0546-­‐d708-­‐71a6e29bede2"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-­‐exc-­‐c14n#"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>W5p2EqlPUbH+mJmT7hUf+0tykXw=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue> t2+F8XtvNTNpDEtHJknhDrJBFKqRCHEzmEHC6kcogKTxfOAmjDSb3IJKqal4aDdN6QcY66rjMV1uVAOXhE6nyxcI/YLr0qzj7O33HILVpFhx READING  SOAP  ISN'T  FUN
  8. $client  =  new  SoapClient('some.wsdl'); $client-­‐>SomeFunction('<rawXml />'); $client-­‐>SomeFunction(['someInfo'  => true]); $client-­‐>SomeFunction(new

     stdClass()); $client-­‐>SomeFunction(new  SoapParam()); $client-­‐>SomeFunction(new  SoapVar()); $client-­‐>SomeFunction(new  MyType());   //  ...
  9. $client-­‐>__getFunctions(); 'array(26)  {  [0]=>  string(70)  "ProductInfo KeywordSearchRequest(KeywordRequest $KeywordSearchRequest)"  [1]=>  string(79)

     "ProductInfo TextStreamSearchRequest(TextStreamRequest $TextStreamSearchRequest)"  [2]=>  string(64)   "ProductInfo PowerSearchRequest(PowerRequest $PowerSearchRequest)"  ...  [23]=>   string(107)  "ShoppingCart RemoveShoppingCartItemsRequest(RemoveShoppingCartItemsRequest $RemoveShoppingCartItemsRequest)"  [24]=>  string(107)  "ShoppingCart ModifyShoppingCartItemsRequest(ModifyShoppingCartItemsRequest $ModifyShoppingCartItemsRequest)"  [25]=>  string(118)  "GetTransactionDetailsResponse GetTransactionDetailsRequest(GetTransactionDetailsRequest $GetTransactionDetailsRequest)"   }';
  10. $client-­‐>__getTypes(); 'array(88)  {  [0]=>  string(30)  "ProductLine ProductLineArray[]"  [1]=>  string(85)  "struct

    ProductLine {  string   Mode;  string  RelevanceRank;  ProductInfo ProductInfo;  }"  [2]=>  string(105)  "struct ProductInfo {  string   TotalResults;  string  TotalPages;  string  ListName;  DetailsArray Details;  }"  ...  [85]=>  string(32)   "ShortSummary ShortSummaryArray[]"  [86]=>  string(121)  "struct GetTransactionDetailsRequest {  string   tag;  string  devtag;  string  key;  OrderIdArray OrderIds;  string  locale;  }"  [87]=>  string(75)  "struct GetTransactionDetailsResponse {  ShortSummaryArray ShortSummaries;  }"  }';
  11. class  MySoapClient extends  SoapClient { public  function  __doRequest( $request, $location,

    $action, $version, $one_way =  0 ) { //  your  logic  here   } }
  12. Classmap class  MyClientClassMap { public  static  function  getCollection():  ClassMapCollection {

    return  new  ClassMapCollection([ new  ClassMap('GetWeatherRequest',  Type\GetWeatherRequest::class), new  ClassMap('GetWeatherResponse',  Type\GetWeatherResponse::class), ]); } }
  13. Type  converters class  DateTypeConverter implements  TypeConverterInterface { const NAMESPACE  =

    'http://www.w3.org/2001/XMLSchema'; const TYPE  = 'date'; public  function  convertXmlToPhp(string  $data) { $doc  = new  DOMDocument(); $doc-­‐>loadXML($data); return  new  DateTime($doc-­‐>textContent); } public  function  convertPhpToXml(DateTime $value):  string { return  sprintf('<%1$s>%2$s</%1$s>',  self::TYPE,  $value-­‐>format('Y-­‐m-­‐d')); } }
  14. Client  API class  YourClient { private  $soapClient; function  getWeather(WeatherRequest $request)

     :  WeatherResponse { return  $this-­‐>soapClient-­‐>getWeather($request); } }
  15. Event  based  client class  YourClient {       function

     call(string $method,  RequestInterface $request):  ResponseInterface { $this-­‐>dispatcher-­‐>dispatch('soap.request',  []); try  { $response  = $this-­‐>soapClient-­‐>{$method}($request); }  catch  (\Exception  $exception)  { $this-­‐>dispatcher-­‐>dispatch('soap.fault',  []); throw  $exception; } $this-­‐>dispatcher-­‐>dispatch('soap.response',  []); return  $response; } }
  16. Event  Listeners class  Logger implements  EventSubscriber {} class  RequestValidator implements

     EventSubscriber {} class  Cacher implements  EventSubscriber {}
  17. HTTP  SoapClient (PSR-­‐7) class  CustomSoapClient extends  SoapClient { private  $handler;

    public  function  __doRequest($request,  $location,  $action,  $version,  $oneWay = 0) { return  $this-­‐>handler-­‐>request($request,  $location,  $action,  $version,  $oneWay); }         }
  18. HTTP  Middleware  (PSR-­‐15) class  BasicAuth implements  MiddlewareInterface {} class  NtlmAuth

    implements  MiddlewareInterface {} class  HttpLogger implements  MiddlewareInterface {} class  HttpCacher implements  MiddlewareInterface {}
  19. SOAP  Extension  Middleware  (PSR-­‐15) class  Wsa implements  MiddlewareInterface {} class

     Wsse implements  MiddlewareInterface {} class  SwaAttachments implements  MiddlewareInterface {} class  MTOMAttachments implements  MiddlewareInterface {}
  20. Ext-­‐soap  bufixing Middleware  (PSR-­‐15) class  DisableWsdlExtensions implements  MiddlewareInterface {} class

     RenameXsdType implements  MiddlewareInterface {} class  RemoveEmptyNode implements  MiddlewareInterface {} class  AddSoapHeader implements  MiddlewareInterface {}
  21. Client  configuration $builder  = new  ClientBuilder( new  ClientFactory(MyCustomClient::class), 'webservice.wsdl', ['cache_wsdl'

     => $shouldCacheWsdl] ); $builder-­‐>addClassMap(new  MyCustomClientClassmap()); $builder-­‐>withHandler(new  GuzzleHandler()) ; $builder-­‐>addMiddleware(new  MyMiddleware()); $client  = $builder-­‐>getClient();
  22. Testing /** *  @test *  @vcr soap-­‐get-­‐city-­‐weather-­‐by-­‐zip-­‐10013.yml */ function  it_should_be_possible_to_hook_php_vcr_for_testing()

    { $result  = $this-­‐>soapClient-­‐>GetCityWeatherByZIP(['ZIP'  => '10013']); $this-­‐>assertTrue($result-­‐>GetCityWeatherByZIPResult-­‐>Success); }