Thursday, November 5, 2009

Contract-First Web Services in practice - part 3 of 5 (PHP)

Welcome to the third article in this series of 5 where I'm putting contract-first service oriented architecture through its paces. For a recap, in article one I defined the rules of engagement, and article two I outlined the contract for the service to be developed. With this behind us, the next challenge is to begin the representation of this service in the 3 programming languages and PHP is first of the blocks.

There are several libraries for use in tackling SOAP in PHP (Pear, Nu Soap, PHP Soap). Not being my forte, I selected the inbuilt soap library PHP Soap that ships with PHP 5. I needed a simple approach that supported WSDL 1.1 and "document literal". I didn't think this would be too much to ask until I discovered that there was an affinity for "RPC encoded" style of web services which fits in well with positional parameters which are well supported in php.

The first step in implementing the service is to create the endpoint handler. This is the php file at the url which will respond to this service and is set in the soap:address element of the wsdl. In the example wsdl the endpoint is "http://localhost:8080/index.php", so the endpoint handler is the index.php. The content for this file is shown below:
  1. <?php
  2. //cache the wsdl
  3. ini_set("soap.wsdl_cache_enabled", "0");
  4. //load the code for the service handler
  5. require_once('./SampleServiceClass.php');
  6. //create a new soap server. Refer to the wsdl file
  7. $server = new SoapServer('SampleService.wsdl');
  8. //set the class which will be handling the request
  9. $server->setClass('SampleServiceClass');
  10. //begin the server
  11. $server->handle();
  12. ?>
  1. The first statement is a directive to cache the wsdl for better performance (suitable for production environments but reasonable in this case as it's contract first so we're taking the WSDL as given).
  2. The next statement imports a file SampleServiceClass.php which we have not yet created. More on this soon.
  3. The third statement creates a soap server instance. The constructor takes as one of its arguments, the wsdl file defined in article 2.
  4. The fourth statement sets the class SampleServiceClass (yet to be defined in the file SampleServiceClass.php)
  5. The final statement starts the server instance.
With those five statements, we're setting up the endpoint by tying in the wsdl to the service handler which we will create in the next step.
  1. <?php
  2. //Exception Types
  3. define('SERVICE_EXCEPTION', 'ServiceException'); //thow this error when the user does not exist
  4. //Soap Exception Fault Codes
  5. define('ERROR_CODE_CLIENT', "Client");
  6. define('ERROR_CODE_SERVER', "Server");
  7. /**
  8. *
  9. * This class handles the Sample Service Soap Requests
  10. *
  11. */
  12. class SampleServiceClass
  13. {
  14. /**
  15. * Create an exception
  16. *
  17. * <ns2:SampleException xmlns:ns2="http://kimenye.com/soap/">
  18. * <message>Some weird error occurred</message>
  19. * </ns2:SampleException>
  20. *
  21. * @param object $exception
  22. * @return
  23. */
  24. function createException($exception,$message)
  25. {
  26. $typedException = new StdClass();
  27. $typedException->message = $message;
  28. $detail = null;
  29. $error_code = ERROR_CODE_CLIENT;
  30. if ($exception == SERVICE_EXCEPTION)
  31. {
  32. $detail->ServiceException = $typedException;
  33. }
  34. return new SoapFault($error_code,$exception,null,$detail,$exception);
  35. }
  36. /**
  37. * This is the sample operation.
  38. *
  39. * @param object $SampleOperationRequest
  40. *
  41. * @return
  42. */
  43. function SampleOperation($SampleOperationRequest)
  44. {
  45. $num_of_args = func_num_args();
  46. //In this example the custom fields are compulsory
  47. if(func_num_args()<1)
  48. {
  49. return $this->createException(SERVICE_EXCEPTION, "SampleOperationRequest -Incorrect parameters passed in.");
  50. }
  51. $Demo = $SampleOperationRequest->DemoField;
  52. $CustomFields = $SampleOperationRequest->SampleFields;
  53. $num_custom_fields = count($CustomFields->SampleField);
  54. /**
  55. * This is to demonstate how to access the custom fields.
  56. *
  57. * If only one custom field is required, access is
  58. *
  59. * $fieldOne = $CustomFields->CustomField->FieldValue
  60. */
  61. if($num_custom_fields == 2)
  62. {
  63. if($CustomFields->SampleField[0]->FieldName == 'a field')
  64. {
  65. $fieldOne = $CustomFields->CustomField[0]->FieldValue;
  66. $fieldTwo = $CustomFields->CustomField[1]->FieldValue;
  67. }
  68. if($CustomFields->SampleField[0]->FieldName == 'another field')
  69. {
  70. $fieldTwo = $CustomFields->CustomField[0]->FieldValue;
  71. $fieldOne = $CustomFields->CustomField[1]->FieldValue;
  72. }
  73. }
  74. $ro1 = new StdClass();
  75. $ro1->DemoField = "Scott";
  76. $custom_logo = new StdClass();
  77. $custom_logo->FieldName = 'logo';
  78. $custom_logo->FieldValue = 'A logo';
  79. $nickname = new StdClass();
  80. $nickname->FieldName = 'nickname';
  81. $nickname->FieldValue = 'Scotty';
  82. $custom_logo2 = new StdClass();
  83. $custom_logo2->FieldName = 'logo';
  84. $custom_logo2->FieldValue = 'A logo';
  85. $nickname2 = new StdClass();
  86. $nickname2->FieldName = 'nickname';
  87. $nickname2->FieldValue = 'Scotty';
  88. $custom_fields = array($custom_logo, $nickname);
  89. $custom_fields2 = array($custom_logo2, $nickname2);
  90. $ro2->SampleFields = $custom_fields;
  91. $ro2 = new StdClass();
  92. $ro2->DemoField = "Hello";
  93. $ro2->SampleFields = $custom_fields2;
  94. $objs = array($ro1, $ro2);
  95. return $objs;
  96. }
  97. }
  98. ?>
This class is a lot more substantial than the previous as it handles the Soap requests.
  1. First, is the definition of a couple of exception constants.
  2. Second, the method "createException" throws the contract specified exception.
  3. Finally, the SampleOperation method reads the input and returns the array output.

That's it!

No comments: