ckWebServicePlugin - 2.2.0

WebService API Plugin.

You are currently browsing
the website for symfony 1

Visit the Symfony2 website


« Back to the Plugins Home

Signin


Forgot your password?
Create an account

Tools

Stats

advanced search
Information Readme Releases Changelog Contribute
Show source

ckWebServicePlugin (for symfony 1.1)

The ckWebServicePlugin allows you to build a webservice api for your symfony applications. It comes with a powerful and easy to use wsdl generator, which creates WS-I compliant wsdl files for a maximum of interopability with PHP, .NET and Java clients.

Installation

To install the latest release, execute:

> symfony plugin:install ckWebServicePlugin

or to install the current revision, checkout the HEAD revision into a plugins/ckWebServicePlugin folder:

> svn co http://svn.symfony-project.com/plugins/ckWebServicePlugin/trunk

Now configure the plugin how it is described in the next section and clear your cache afterwards.

Configuration

The configuration can be devided into two parts. A basic one, which is mandatory and has to be done in order to get the plugin working. The second, advanced, part is only required under certain circumstances and when you want to leverage the full power of the plugin. So if you are using this plugin the first time you can skip the Advanced section.

Basic

app.yml

Configure general plugin settings in your application's app.yml file.

# your environment for webservice mode
soap:
  # enable the `ckSoapParameterFilter`
  enable_soap_parameter: on
  ck_web_service_plugin:
    # the location of your wsdl file, relative to your project's `web/` folder
    wsdl: myWebService.wsdl 
    # the class that will be registered as handler for webservice requests
    handler: ckSoapHandler

You will propably have to change the wsdl and handler option after you have run the webservice:generate-wsdl task.

factories.yml

Enable the ckWebServiceController in your application's factories.yml file.

# your environment for webservice mode
soap:
  controller:
    class: ckWebServiceController

filters.yml

Enable the ckSoapParameterFilter in your application's filters.yml file.

soap_parameter:
  class: ckSoapParameterFilter
  param:
    # `app_enable_soap_parameter` has to be set to `on` so the filter is only enabled in soap mode
    condition: %APP_ENABLE_SOAP_PARAMETER%

Advanced

app.yml

In your application's app.yml file you have some more options to configure the internally used SoapServer.

These are:

  • setting the persistence mode:

    # your environment for webservice mode
    soap:
      # ...
      ck_web_service_plugin:
      # ...
      persist: %SOAP_PERSISTENCE_SESSION%
    

    For further information see documentation on SoapServer::setPersistence().

  • setting the $options array used by SoapServer::__construct():

    # your environment for webservice mode
    soap:
      # ...
      ck_web_service_plugin:
      # ...
      soap_options:
        encoding: utf-8
        soap_version: %SOAP_1_2%
    

    For further information see documentation on SoapServer::__construct().

  • configuring SOAP Headers:

    # your environment for webservice mode
    soap:
      # ...
      ck_web_service_plugin:
      # ...
      soap_headers:
        # the name of the soap header
        MySoapHeader: 
          # the corresponding data class
          class: MySoapHeaderDataClass
    

    For more details about the usage of SOAP Headers read the section Using SOAP Headers

module.yml

Every action, which should be callable in webservice mode, needs some configuration so the parameters are accessable through sfRequest::getParameter() and the proper value is returned as result. This configuration is automaticly done by the webservice:generate-wsdl task, if you don't use the task or want to customize something you have to change the module.yml file corresponding to the action.

An example module.yml file:

# your environment for webservice mode
soap:
  # the action name
  action_name:
    # ordered list of the parameters
    parameter: [first_param, second_param]
    # the result adapter
    result:
      # the result adapter class, extending `ckAbstractResultAdapter`
      class: ckPropertyResultAdapter
      # result adapter specific parameters array
      param:
        member: result

The result adapters will be explained in more detail in the section Understanding result adapters.

Using the webservice:generate-wsdl task

Now it is time to start making our actions avialable as a webservice.

This is best explained with an example, we will use the following action, which will just multiply two numbers and is in an application frontend:

<?php
 
class MathActions extends sfActions
{
  /**
   * An action multiplying two numbers.
   *
   * @param sfRequest $request A sfRequest instance
   */
  public function executeMultiply($request)
  {
    $factorA = $request->getParameter('a');
    $factorB = $request->getParameter('b');
 
    if(is_numeric($factorA) && is_numeric($factorB))
    {
      $this->result = $factorA * $factorB;
 
      return sfView::SUCCESS;    
    }
    else
    {
      return sfView::ERROR;
    }        
  }
}

The only thing we will have to do is updating the doc comment:

<?php
 
class MathActions extends sfActions
{
  /**
   * An action multiplying two numbers.
   *
   * @ws-enable
   *
   * @param float $a Factor A
   * @param float $b Factor B
   *
   * @return float The result
   */
  public function executeMultiply($request)
  {
    // nothing changed here...        
  }
}

So what has changed:

  • a @ws-enable doc tag was added to mark the action as available in webservice mode,
  • for every parameter accessable through sfRequest::getParameter() a @param doc tag with type, name and description was added,
  • a @return doc tag with type and description was added,
  • the @param doc tag for $request was removed, because it is not a real parameter required by the action multiply.

Now we are ready to execute the webservice:generate-wsdl task. It is explained in detail in the section Thewebservice:generate-wsdltask in detail.

Execute the task with:

> symfony webservice:generate-wsdl frontend MathApi http://localhost/

Change http://localhost/ to the url you are using for development!

The task will generate a MathApi.wsdl and MathApi.php in your project's web/ folder.

We have to change the wsdl option in the application's app.yml file to MathApi.wsdl:

# your environment for webservice mode
soap:
  # ...
  ck_web_service_plugin:
    # the location of your wsdl file, relative to your project's `web/` folder
    wsdl: MathApi.wsdl

and we have to clear the cache.

We are now able to call the multiply action, with PHP's SoapClient, a simple test script might look like this:

<?php
 
$client = new SoapClient('http://localhost/MathApi.wsdl');
 
$result = $client->math_multiply(2, 5);
 
if($result == 10)
{
  echo 'Test succeded!';
}
else
{
  echo 'Test failed!';
}
 
?>

You see the name of the webservice method follows the scheme 'moduleName'_'actionName', because this might be not descriptive enough or an alternative scheme is desired, we will see how to change this. For this we have to change again the action's doc comment:

<?php
 
class MathActions extends sfActions
{
  /**
   * An action multiplying two numbers.
   *
   * @ws-enable
   * @ws-method SimpleMultiply
   *
   * @param float $a Factor A
   * @param float $b Factor B
   *
   * @return float The result
   */
  public function executeMultiply($request)
  {
    // nothing changed here...        
  }
}

So what has changed:

  • a @ws-method doc tag was added, specifying the new name.

Now we have to regenerate the wsdl, execute:

> symfony webservice:generate-wsdl -h frontend MathApi http://localhost/


The h (handler) option was added to the command, if it is not set the @ws-method doc tag is ignored.

The handler option leads to the generation of a MathApiHandler.class.php in the application's lib/ folder. To enable this handler we have to edit the application's app.yml file and set the handler to MathApiHandler:

soap:
  # ...
  ck_web_service_plugin:
    # ...
    # the class that will be registered as handler for webservice requests
    handler: MathApiHandler

Our test script has to be changed the following way:

<?php
 
$client = new SoapClient('http://localhost/MathApi.wsdl');
 
$result = $client->SimpleMultiply(2, 5);
 
// nothing changed here...
 
?>

You now have a basic overview how to use the plugin, the following sections will explain more advanced features.

The webservice:generate-wsdl task in detail

Its general syntax is:

> symfony webservice:generate-wsdl [--environment=soap] [--debug] [--handler] app_name webservice_name webservice_base_url

It will do the following things:

  • look through all modules of 'app_name' for actions with the @ws-enable doc tag,
  • add the marked actions to a wsdl definition,
  • save the wsdl definition to your project's web/ folder as 'webservice_name'.wsdl,
  • create a new controller in your project's web/ folder with name 'webservice_name'.php,
  • if the handler option is set, it will add a 'webservice_name'Handler.class.php to the 'app_name''s lib/ folder.

The arguments explained in detail:

  • app_name:
    • specifies the application, which is searched for actions marked with the @ws-enable doc tag
  • webservice_name:
    • specifies the name of the webservice
  • webservice_base_url:
    • specifies the url under which the webservice will be accessable

The options explained in detail:

  • environment (short e):
    • sets the environment for webservice mode
    • defaults to soap
    • if you change it, don't forget to change the configuration files accordingly
  • debug (short d):
    • enables the debug mode in the generated controller
    • defaults to false
  • handler (short h):
    • generates a custom SOAP handler extending the ckSoapHandler
    • defaults to false

Understanding result adapters

Until now it wasn't explained how the result of an action is got, we have just seen, that the result was assigned to the $this->result property and a sfView constant was returned, like sfView::SUCCESS.

Because an action should be reusable in web and webservice mode, we can't rely on the return value, because in web mode it always has to be a template name. For this reason the result adpater pattern was introduced. This means to get the action result an adapter object of a subclass of ckAbstractResultAdapter is used. Which one is used is determined by the configuration in the action's module.yml file how it is shown in the Configuration->Advanced->module.yml section. The param array in the module.yml file is passed to the result adapter's constructor and contains adapter specific settings.

There are three built-in adapters:

  • ckPropertyResultAdapter:
    • gets the result from a property
    • parameters:
      • property:
        • specifies the property name
        • defaults to result
    • if there is only one property, this one is returned, also it name doesn't match the specified member
  • ckMethodResultAdapter:
    • gets the result from a method call on the action object
    • parameters:
      • method:
        • specifies the method name
  • ckRenderResultAdapter:
    • executes the standard render pipeline and returns the resulting text
    • if this adapter is used the return value has to be string
    • parameters:
      • none

You can easily implement your own adapters by extending the ckAbstractResultAdapter class and overriding the abstract ckAbstractResultAdapter::getResult() method.

Using arrays and objects as parameters or result values

In the previous examples only simple types have been used for parameters and result values, but you propably want to use objects, arrays of simple types or arrays of complex types. To illustrate these features we will stick to the example used earlier.

Let's say we want to multiply any number of factors, not only two:

<?php
 
class MathActions extends sfActions
{
  /**
   * An action multiplying any number of factors.
   *
   * @ws-enable
   * @ws-method SimpleMultiply
   *
   * @param float[] $factors An array of factors
   *
   * @return float The result
   */
  public function executeMultiply($request)
  {
    $this->result = 1;
 
    foreach($request->getParameter('factors') as $factor)
    {
      $this->result *= factor;
    }
  }
}

So what has changed:

  • the @param doc tags for factor $a and $b have been replaced with one @param doc tag for the factors array,
  • the action body changed to iterate over the array of factors.

As you can see the array type is indicated by the [], you can add the square brackets to any type to identify an array of the type should be used.

Because array types are complex data types, we have to add a mapping to the application's app.yml file:

soap:
  # ...
  ck_web_service_plugin:
    # ...
    soap_options:
      classmap:
        # mapping of wsdl types to PHP types
        FloatArray: ckGenericArray

The generated array type names follow the scheme 'TypeName'Array.

Use ckGenericArray as PHP mapping type, so you can use the transferred array object like a normal PHP array (iterate, index, ...).

The last thing to do is: regenerate the wsdl file with the webservice:generate-wsdl task, don't forget the h option, and clear the cache.

Our test client might look like this now:

<?php
 
$client = new SoapClient('http://localhost/MathApi.wsdl');
 
$result = $client->SimpleMultiply(array(1, 2, 3, 4));
 
if($result == 24)
{
  echo 'Test succeded!';
}
else
{
  echo 'Test failed!';
}
 
?>

As example for the use of classes, we will implement the multiplication example for complex numbers.

Because complex numbers aren't nativly supported in PHP, we have to create our own ComplexNumber.class.php in the applications lib/ folder with the following content:

<?php
 
class ComplexNumber
{
  /**
   * @var float
   */
  public $realPart;
 
  /**
   * @var float
   */
  public $imaginaryPart;
 
  public function __construct($realPart, $imaginaryPart)
  {
    $this->realPart      = $realPart;
    $this->imaginaryPart = $imaginaryPart;
  }
 
  public function __toString()
  {
    return sprintf('%.2f + %.2fi', $this->realPart, $this->imaginaryPart);
  }
 
  public function multiply($c)
  {
    $real      = $this->realPart * $c->realPart - $this->imaginaryPart * $c->imaginaryPart;
    $imaginary = $this->realPart * $c->imaginaryPart - $this->imaginaryPart * $c->realPart;
    return new ComplexNumber($real, $imaginary);
  }
}

It is important to add the @var doc tag with the type and an optional desciption to the properties of the class, so they will appear in the wsdl file.

Now let's modify the MathActions class by adding a new action, called ComplexMultiply:

<?php
 
class MathActions extends sfActions
{
  // nothing changed here...
 
  /**
   * An action multiplying any number of complex factors.
   *
   * @ws-enable
   * @ws-method ComplexMultiply
   *
   * @param ComplexNumber[] $input
   *
   * @return ComplexNumber
   */
  public function executeComplexMultiply($request)
  {
    $this->result = new ComplexNumber(1, 0);
 
    foreach($request->getParameter('input') as $c)
    {
      $this->result = $this->result->multiply($c);
    }
  }
}

Again we have to update the classmap in the application's app.yml file:

soap:
  # ...
  ck_web_service_plugin:
    # ...
    soap_options:
      classmap:
        # mapping of wsdl types to PHP types
        FloatArray:         ckGenericArray
        ComplexNumber:      ComplexNumber
        ComplexNumberArray: ckGenericArray

Last regenerate the wsdl file once more and clear the cache.

Our updated test client will look something like this:

<?php
 
class ComplexNumber
{
  public $realPart;
 
  public $imaginaryPart;
 
  public function __construct($realPart, $imaginaryPart)
  {
    $this->realPart      = $realPart;
    $this->imaginaryPart = $imaginaryPart;
  }
}
 
$client = new SoapClient('http://localhost/MathApi.wsdl', array('classmap' => array('ComplexNumber' => 'ComplexNumber')));
 
$cn = new ComplexNumber(1, 0);
 
$result = $client->ComplexMultiply(array($cn, $cn));
 
if($result == $cn)
{
  echo 'Test succeded!';
}
else
{
  echo 'Test failed!';
}
 
?>

As we see, we can use a lightweight version of the ComplexNumber class, the only important thing is, that the properties are the same as defined in the wsdl file.

In this section you have learned how to work with arrays and classes, the next section covers the usage of SOAP Headers.

Using SOAP Headers

SOAP Headers provide a way to send additional information, which are not directly or semantically related to the original method call. An good example for this are authentication information, so the use of a certain method can be restricted to a group of users.

To demonstrate the support for SOAP Headers, we will stick to the simple multiplication example used previously.

First we will modify the MathActions class the following way:

<?php
 
class MathActions extends sfActions
{
  /**
   * An action multiplying two numbers.
   *
   * @ws-enable
   * @ws-method SimpleMultiply
   * @ws-header AuthHeader: AuthData
   *
   * @param float $a Factor A
   * @param float $b Factor B
   *
   * @return float The result
   */
  public function executeMultiply($request)
  {
    $factorA = $request->getParameter('a');
    $factorB = $request->getParameter('b');
 
    if($this->getUser()->isAuthenticated() && is_numeric($factorA) && is_numeric($factorB))
    {
      $this->result = $factorA * $factorB;
 
      return sfView::SUCCESS;    
    }
    else
    {
      return sfView::ERROR;
    }
  }
}

The changes are:

  • a @ws-header doc tag was added, specifying the name (AuthHeader) of the SOAP Header and the data class (AuthData), which holds the data of the SOAP header,
  • an authentication check was added, so the multiplication is only done, if the user was authenticated successfully.

To get this example working we have to define the AuthData class, so let's create a AuthData.class.php file in the application's lib folder with the following content:

<?php
 
class AuthData
{
  /**
   * @var string
   */
   public $username;
 
   /**
    * @var string
    */
   public $password;
}

Afterwards we have to edit the application's app.yml file:

soap:
  # ...
  ck_web_service_plugin:
    # ...
    soap_headers:
      AuthHeader:
        class: AuthData

When the application receives a SOAP Header a webservice.handle_header event is dispatched (it is a notifyUntil event), it has two attributes, the first is header holding the name of the header and the second is data containing an instance of the header's data class. To do the authentication stuff in our example we will define an AuthHeaderListener class by creating an AuthHeaderListener.class.php in the application's lib/ folder with the following content:

<?php
 
class AuthHeaderListener
{
  const HEADER = 'AuthHeader';
 
  public function handleAuthHeader($event)
  {
    if($event['header'] == self::HEADER)
    {
      if($event['data']->username == 'test' && $event['data']->password == 'secret')
      {
        sfContext::getInstance()->getUser()->setAuthenticated(true);
      }
 
      return true;
    }
    else
    {
      return false;
    }
  }
}

We have to register this event listener in the application's configuration class (assuming the application's name is frontend, this would be frontendConfiguration.class.php).

The modified configuration class would look something like this:

<?php
 
class frontendConfiguration
{
  public function configure()
  {
    $this->dispatcher->connect('webservice.handle_header', array('AuthHeaderListener', 'handleAuthHeader'));
  }
}

The example is now ready to work, regenerate the wsdl file and clear the cache.

The last missing thing is a simple test script:

<?php
 
class AuthData
{
  public $username;
  public $password;
}    
 
$client = new SoapClient('http://localhost/MathApi.wsdl', array('classmap' => array('AuthHeader' => 'AuthData')));
 
$authData = new AuthData();
$authData->username = 'test';
$authData->password = 'secret';
 
$authHeader = new SoapHeader('http://localhost/', 'AuthHeaderElement', $authData);
 
$result = $client->__soapCall('SimpleMultiply', array(5, 2), null, array($authHeader), $outHeaders);
 
if($result == 10 && $outHeaders['AuthHeaderElement'] == $authData)
{
  echo 'Test succeded!';
}
else
{
  echo 'Test failed!';
}
 
?>

When creating a new SoapHeader object with SoapHeader::__construct() the name of the SOAP Header has to end with Element.

Reference

Supported simple types

All primitive PHP types are supported:

  • string maps to xsd:string
  • int or integer maps to xsd:int
  • float maps to xsd:float
  • boolean or bool maps to xsd:boolean

Tips'n Tricks

Disable wsdl caching during development

If you often regenerate your wsdl file during development, you propably want to disable caching of this file, so changes become usable immidiatly.

You can do this by modifying your php.ini:

soap.wsdl_cache_enabled=0

Support

If you have any questions concerning the use of the plugin, send me an email to: christian-kerl [at] web [dot] de

Contribution

If you have feature suggestions, bug reports, patches, usage examples for the documentation or want to become an active contributor, send me an email to: christian-kerl [at] web [dot] de

Any help is welcome!