added initial support for service binding

This commit is contained in:
Christian Kerl 2010-10-08 14:24:42 +02:00
parent 31d40380a6
commit 8d3743d928
18 changed files with 379 additions and 68 deletions

View File

@ -31,12 +31,19 @@ class WebServiceExtension extends Extension
$configuration->setAlias('http_kernel', 'webservice.kernel'); $configuration->setAlias('http_kernel', 'webservice.kernel');
} }
if(!isset($config['definition'])) if(isset($config['definition']))
{
$this->registerServiceDefinitionConfig($config['definition'], $configuration);
}
else
{ {
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
} }
$this->registerServiceDefinitionConfig($config['definition'], $configuration); if(isset($config['binding']))
{
$this->registerServiceBindingConfig($config['binding'], $configuration);
}
} }
protected function registerServiceDefinitionConfig(array $config, ContainerBuilder $configuration) protected function registerServiceDefinitionConfig(array $config, ContainerBuilder $configuration)
@ -50,6 +57,30 @@ class WebServiceExtension extends Extension
$configuration->setParameter('webservice.definition.resource', isset($config['resource']) ? $config['resource'] : null); $configuration->setParameter('webservice.definition.resource', isset($config['resource']) ? $config['resource'] : null);
} }
protected function registerServiceBindingConfig(array $config, ContainerBuilder $configuration)
{
$style = isset($config['style']) ? $config['style'] : 'document-literal-wrapped';
if(!in_array($style, array('document-literal-wrapped', 'rpc-literal')))
{
throw new \InvalidArgumentException();
}
$binderNamespace = 'Bundle\\WebServiceBundle\\ServiceBinding\\';
switch ($style)
{
case 'document-literal-wrapped':
$configuration->setParameter('webservice.binder.request.class', $binderNamespace . 'DocumentLiteralWrappedRequestMessageBinder');
$configuration->setParameter('webservice.binder.response.class', $binderNamespace . 'DocumentLiteralWrappedResponseMessageBinder');
break;
case 'rpc-literal':
$configuration->setParameter('webservice.binder.request.class', $binderNamespace . 'RpcLiteralRequestMessageBinder');
$configuration->setParameter('webservice.binder.response.class', $binderNamespace . 'RpcLiteralResponseMessageBinder');
break;
}
}
public function getXsdValidationBasePath() public function getXsdValidationBasePath()
{ {
return null; return null;

View File

@ -6,9 +6,12 @@
<parameters> <parameters>
<parameter key="request.class">Bundle\WebServiceBundle\Soap\SoapRequest</parameter> <parameter key="request.class">Bundle\WebServiceBundle\Soap\SoapRequest</parameter>
<parameter key="webservice.binder.request.class">Bundle\WebServiceBundle\ServiceBinding\DocumentLiteralWrappedRequestMessageBinder</parameter>
<parameter key="webservice.binder.response.class">Bundle\WebServiceBundle\ServiceBinding\DocumentLiteralWrappedResponseMessageBinder</parameter>
<parameter key="webservice.definition.class">Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition</parameter> <parameter key="webservice.definition.class">Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition</parameter>
<parameter key="webservice.definition.loader.class">Bundle\WebServiceBundle\ServiceDefinition\Loader\XmlFileLoader</parameter> <parameter key="webservice.definition.loader.class">Bundle\WebServiceBundle\ServiceDefinition\Loader\XmlFileLoader</parameter>
<parameter key="webservice.definition.dumper.class">Bundle\WebServiceBundle\ServiceDefinition\Dumper\Wsdl11DocumentLiteralFileDumper</parameter> <parameter key="webservice.definition.dumper.class">Bundle\WebServiceBundle\ServiceDefinition\Dumper\Wsdl11DocumentLiteralWrappedFileDumper</parameter>
</parameters> </parameters>
<services> <services>
@ -19,12 +22,22 @@
</service> </service>
<service id="webservice.kernel" class="Bundle\WebServiceBundle\SoapKernel"> <service id="webservice.kernel" class="Bundle\WebServiceBundle\SoapKernel">
<argument type="service" id="webservice.definition" /> <argument type="service" id="webservice.binder" />
<argument type="service" id="webservice.definition.loader" />
<argument type="service" id="webservice.definition.dumper" />
<argument type="service" id="symfony_http_kernel" /> <argument type="service" id="symfony_http_kernel" />
</service> </service>
<service id="webservice.binder" class="Bundle\WebServiceBundle\ServiceBinding\ServiceBinder">
<argument type="service" id="webservice.definition" />
<argument type="service" id="webservice.definition.loader" />
<argument type="service" id="webservice.definition.dumper" />
<argument type="service" id="webservice.binder.request" />
<argument type="service" id="webservice.binder.response" />
</service>
<service id="webservice.binder.request" class="%webservice.binder.request.class%" />
<service id="webservice.binder.response" class="%webservice.binder.response.class%" />
<service id="webservice.definition" class="%webservice.definition.class%" shared="true"> <service id="webservice.definition" class="%webservice.definition.class%" shared="true">
<argument type="string">%webservice.definition.name%</argument> <argument type="string">%webservice.definition.name%</argument>
</service> </service>

View File

@ -0,0 +1,26 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
class DocumentLiteralWrappedRequestMessageBinder implements MessageBinderInterface
{
public function processMessage(Method $messageDefinition, $message)
{
if(count($message) > 1)
{
throw new \InvalidArgumentException();
}
$result = array();
$message = $message[0];
foreach($messageDefinition->getArguments() as $argument)
{
$result[$argument->getName()] = $message->{$argument->getName()};
}
return $result;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
class DocumentLiteralWrappedResponseMessageBinder implements MessageBinderInterface
{
public function processMessage(Method $messageDefinition, $message)
{
$result = new \stdClass();
$result->{$messageDefinition->getName() . 'Result'} = $message;
return $result;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
interface MessageBinderInterface
{
/**
*
*
* @param Method $messageDefinition
* @param mixed $message
*
* @return mixed
*/
function processMessage(Method $messageDefinition, $message);
}

View File

@ -0,0 +1,22 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
class RpcLiteralRequestMessageBinder implements MessageBinderInterface
{
public function processMessage(Method $messageDefinition, $message)
{
$result = array();
$i = 0;
foreach($messageDefinition->getArguments() as $argument)
{
$result[$argument->getName()] = $message[$i];
$i++;
}
return $result;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
class RpcLiteralResponseMessageBinder implements MessageBinderInterface
{
public function processMessage(Method $messageDefinition, $message)
{
return $message;
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace Bundle\WebServiceBundle\ServiceBinding;
use Bundle\WebServiceBundle\Soap\SoapHeader;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
use Bundle\WebServiceBundle\ServiceDefinition\Header;
use Bundle\WebServiceBundle\ServiceDefinition\Dumper\DumperInterface;
use Bundle\WebServiceBundle\ServiceDefinition\Loader\LoaderInterface;
class ServiceBinder
{
/**
* @var \Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition
*/
private $definition;
/**
* @var \Bundle\WebServiceBundle\ServiceDefinition\Dumper\DumperInterface
*/
private $definitionDumper;
/**
* @var \Bundle\WebServiceBundle\ServiceBinding\MessageBinderInterface
*/
private $requestMessageBinder;
/**
* @var \Bundle\WebServiceBundle\ServiceBinding\MessageBinderInterface
*/
private $responseMessageBinder;
public function __construct(
ServiceDefinition $definition,
LoaderInterface $loader,
DumperInterface $dumper,
MessageBinderInterface $requestMessageBinder,
MessageBinderInterface $responseMessageBinder
)
{
$loader->loadServiceDefinition($definition);
$this->definition = $definition;
$this->definitionDumper = $dumper;
$this->requestMessageBinder = $requestMessageBinder;
$this->responseMessageBinder = $responseMessageBinder;
}
public function getSerializedServiceDefinition()
{
// TODO: add caching
return $this->definitionDumper->dumpServiceDefinition($this->definition);
}
public function isServiceHeader($name)
{
return $this->definition->getHeaders()->has($name);
}
public function isServiceMethod($name)
{
return $this->definition->getMethods()->has($name);
}
public function processServiceHeader($name, $data)
{
$headerDefinition = $this->definition->getHeaders()->get($name);
return $this->createSoapHeader($headerDefinition, $data);
}
public function processServiceMethodArguments($name, $arguments)
{
$methodDefinition = $this->definition->getMethods()->get($name);
$result = array();
$result['_controller'] = $methodDefinition->getController();
$result = array_merge($result, $this->requestMessageBinder->processMessage($methodDefinition, $arguments));
return $result;
}
public function processServiceMethodReturnValue($name, $return)
{
$methodDefinition = $this->definition->getMethods()->get($name);
return $this->responseMessageBinder->processMessage($methodDefinition, $return);
}
protected function createSoapHeader(Header $headerDefinition, $data)
{
if(!preg_match('/^\{(.+)\}(.+)$/', $headerDefinition->getType()->getXmlType(), $matches))
{
throw new \InvalidArgumentException();
}
return new SoapHeader($matches[1], $matches[2], $data);
}
}

View File

@ -2,12 +2,12 @@
namespace Bundle\WebServiceBundle\ServiceDefinition; namespace Bundle\WebServiceBundle\ServiceDefinition;
class ServiceHeader class Argument
{ {
private $name; private $name;
private $type; private $type;
public function __construct($name = null, ServiceType $type = null) public function __construct($name = null, Type $type = null)
{ {
$this->setName($name); $this->setName($name);
$this->setType($type); $this->setType($type);

View File

@ -0,0 +1,35 @@
<?php
namespace Bundle\WebServiceBundle\ServiceDefinition;
class Header
{
private $name;
private $type;
public function __construct($name = null, Type $type = null)
{
$this->setName($name);
$this->setType($type);
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getType()
{
return $this->type;
}
public function setType($type)
{
$this->type = $type;
}
}

View File

@ -2,13 +2,11 @@
namespace Bundle\WebServiceBundle\ServiceDefinition\Loader; namespace Bundle\WebServiceBundle\ServiceDefinition\Loader;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceType;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition; use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
use Bundle\WebServiceBundle\ServiceDefinition\Header;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceMethod; use Bundle\WebServiceBundle\ServiceDefinition\Method;
use Bundle\WebServiceBundle\ServiceDefinition\Argument;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceHeader; use Bundle\WebServiceBundle\ServiceDefinition\Type;
class XmlFileLoader extends FileLoader class XmlFileLoader extends FileLoader
{ {
@ -35,11 +33,11 @@ class XmlFileLoader extends FileLoader
/** /**
* @param \SimpleXMLElement $node * @param \SimpleXMLElement $node
* *
* @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceHeader * @return \Bundle\WebServiceBundle\ServiceDefinition\Header
*/ */
protected function parseHeader(\SimpleXMLElement $node) protected function parseHeader(\SimpleXMLElement $node)
{ {
$header = new ServiceHeader((string)$node['name'], $this->parseType($node->type)); $header = new Header((string)$node['name'], $this->parseType($node->type));
return $header; return $header;
} }
@ -47,11 +45,16 @@ class XmlFileLoader extends FileLoader
/** /**
* @param \SimpleXMLElement $node * @param \SimpleXMLElement $node
* *
* @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceMethod * @return \Bundle\WebServiceBundle\ServiceDefinition\Method
*/ */
protected function parseMethod(\SimpleXMLElement $node) protected function parseMethod(\SimpleXMLElement $node)
{ {
$method = new ServiceMethod((string)$node['name'], (string)$node['controller']); $method = new Method((string)$node['name'], (string)$node['controller']);
foreach($node->argument as $argument)
{
$method->getArguments()->add($this->parseArgument($argument));
}
return $method; return $method;
} }
@ -59,7 +62,19 @@ class XmlFileLoader extends FileLoader
/** /**
* @param \SimpleXMLElement $node * @param \SimpleXMLElement $node
* *
* @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceType * @return \Bundle\WebServiceBundle\ServiceDefinition\Argument
*/
protected function parseArgument(\SimpleXMLElement $node)
{
$argument = new Argument((string)$node['name'], $this->parseType($node->type));
return $argument;
}
/**
* @param \SimpleXMLElement $node
*
* @return \Bundle\WebServiceBundle\ServiceDefinition\Type
*/ */
protected function parseType(\SimpleXMLElement $node) protected function parseType(\SimpleXMLElement $node)
{ {
@ -67,7 +82,7 @@ class XmlFileLoader extends FileLoader
$qname = explode(':', $node['xml-type'], 2); $qname = explode(':', $node['xml-type'], 2);
$xmlType = sprintf('{%s}%s', $namespaces[$qname[0]], $qname[1]); $xmlType = sprintf('{%s}%s', $namespaces[$qname[0]], $qname[1]);
$type = new ServiceType((string)$node['php-type'], $xmlType, (string)$node['converter']); $type = new Type((string)$node['php-type'], $xmlType, (string)$node['converter']);
return $type; return $type;
} }

View File

@ -2,15 +2,19 @@
namespace Bundle\WebServiceBundle\ServiceDefinition; namespace Bundle\WebServiceBundle\ServiceDefinition;
class ServiceMethod use Bundle\WebServiceBundle\Util\Collection;
class Method
{ {
private $name; private $name;
private $controller; private $controller;
private $arguments;
public function __construct($name = null, $controller = null) public function __construct($name = null, $controller = null, array $arguments = array())
{ {
$this->setName($name); $this->setName($name);
$this->setController($controller); $this->setController($controller);
$this->setArguments($arguments);
} }
public function getName() public function getName()
@ -32,4 +36,15 @@ class ServiceMethod
{ {
$this->controller = $controller; $this->controller = $controller;
} }
public function getArguments()
{
return $this->arguments;
}
public function setArguments($arguments)
{
$this->arguments = new Collection('getName');
$this->arguments->addAll($arguments);
}
} }

View File

@ -2,7 +2,7 @@
namespace Bundle\WebServiceBundle\ServiceDefinition; namespace Bundle\WebServiceBundle\ServiceDefinition;
class ServiceType class Type
{ {
private $phpType; private $phpType;
private $xmlType; private $xmlType;

View File

@ -46,7 +46,6 @@ class SoapRequest extends Request
parent::__construct($query, null, $attributes, $cookies, null, $server); parent::__construct($query, null, $attributes, $cookies, null, $server);
$this->rawContent = $rawContent != null ? $rawContent : $this->loadRawContent(); $this->rawContent = $rawContent != null ? $rawContent : $this->loadRawContent();
$this->soapHeaders = new Collection('getName'); $this->soapHeaders = new Collection('getName');
} }

View File

@ -26,6 +26,9 @@ class SoapResponse extends Response
*/ */
protected $soapHeaders; protected $soapHeaders;
/**
* @var mixed
*/
protected $soapReturnValue; protected $soapReturnValue;
public function __construct($returnValue = null) public function __construct($returnValue = null)

View File

@ -10,12 +10,6 @@
namespace Bundle\WebServiceBundle; namespace Bundle\WebServiceBundle;
use Bundle\WebServiceBundle\Soap\SoapHeader;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceHeader;
use Bundle\WebServiceBundle\ServiceDefinition\Dumper\DumperInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -23,9 +17,9 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
use Bundle\WebServiceBundle\Soap\SoapRequest; use Bundle\WebServiceBundle\Soap\SoapRequest;
use Bundle\WebServiceBundle\Soap\SoapResponse; use Bundle\WebServiceBundle\Soap\SoapResponse;
use Bundle\WebServiceBundle\Soap\SoapHeader;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition; use Bundle\WebServiceBundle\ServiceBinding\ServiceBinder;
use Bundle\WebServiceBundle\ServiceDefinition\Loader\LoaderInterface;
use Bundle\WebServiceBundle\Util\String; use Bundle\WebServiceBundle\Util\String;
@ -55,24 +49,20 @@ class SoapKernel implements HttpKernelInterface
protected $soapResponse; protected $soapResponse;
/** /**
* @var \Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition * @var \Bundle\WebServiceBundle\ServiceBinding\ServiceBinder
*/ */
protected $serviceDefinition; protected $serviceBinder;
/** /**
* @var \Symfony\Component\HttpKernel\HttpKernelInterface * @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/ */
protected $kernel; protected $kernel;
public function __construct(ServiceDefinition $definition, LoaderInterface $loader, DumperInterface $dumper, HttpKernelInterface $kernel) public function __construct(ServiceBinder $serviceBinder, HttpKernelInterface $kernel)
{ {
$this->serviceDefinition = $definition; $this->serviceBinder = $serviceBinder;
$loader->loadServiceDefinition($this->serviceDefinition);
// assume $dumper creates WSDL 1.1 file $this->soapServer = new \SoapServer($this->serviceBinder->getSerializedServiceDefinition());
$wsdl = $dumper->dumpServiceDefinition($this->serviceDefinition);
$this->soapServer = new \SoapServer($wsdl);
$this->soapServer->setObject($this); $this->soapServer->setObject($this);
$this->kernel = $kernel; $this->kernel = $kernel;
@ -107,19 +97,21 @@ class SoapKernel implements HttpKernelInterface
*/ */
public function __call($method, $arguments) public function __call($method, $arguments)
{ {
if($this->serviceDefinition->getHeaders()->has($method)) if($this->serviceBinder->isServiceHeader($method))
{ {
// collect request soap headers // collect request soap headers
$headerDefinition = $this->serviceDefinition->getHeaders()->get($method); $this->soapRequest->getSoapHeaders()->add(
$this->soapRequest->getSoapHeaders()->add($this->createSoapHeader($headerDefinition, $arguments[0])); $this->serviceBinder->processServiceHeader($method, $arguments[0])
);
return; return;
} }
if($this->serviceDefinition->getMethods()->has($method)) if($this->serviceBinder->isServiceMethod($method))
{ {
$methodDefinition = $this->serviceDefinition->getMethods()->get($method); $this->soapRequest->attributes->add(
$this->soapRequest->attributes->set('_controller', $methodDefinition->getController()); $this->serviceBinder->processServiceMethodArguments($method, $arguments)
);
// delegate to standard http kernel // delegate to standard http kernel
$response = $this->kernel->handle($this->soapRequest, self::MASTER_REQUEST, true); $response = $this->kernel->handle($this->soapRequest, self::MASTER_REQUEST, true);
@ -133,7 +125,10 @@ class SoapKernel implements HttpKernelInterface
} }
// return operation return value to soap server // return operation return value to soap server
return $this->soapResponse->getReturnValue(); return $this->serviceBinder->processServiceMethodReturnValue(
$method,
$this->soapResponse->getReturnValue()
);
} }
} }
@ -180,14 +175,4 @@ class SoapKernel implements HttpKernelInterface
return $response; return $response;
} }
protected function createSoapHeader(ServiceHeader $headerDefinition, $data)
{
if(!preg_match('/^\{(.+)\}(.+)$/', $headerDefinition->getType()->getXmlType(), $matches))
{
throw new \InvalidArgumentException();
}
return new SoapHeader($matches[1], $matches[2], $data);
}
} }

View File

@ -10,17 +10,20 @@
namespace Bundle\WebServiceBundle\Tests; namespace Bundle\WebServiceBundle\Tests;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceMethod;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Bundle\WebServiceBundle\SoapKernel; use Bundle\WebServiceBundle\SoapKernel;
use Bundle\WebServiceBundle\Soap\SoapRequest; use Bundle\WebServiceBundle\Soap\SoapRequest;
use Bundle\WebServiceBundle\Soap\SoapResponse; use Bundle\WebServiceBundle\Soap\SoapResponse;
use Bundle\WebServiceBundle\ServiceBinding\ServiceBinder;
use Bundle\WebServiceBundle\ServiceBinding\RpcLiteralResponseMessageBinder;
use Bundle\WebServiceBundle\ServiceBinding\RpcLiteralRequestMessageBinder;
use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
use Bundle\WebServiceBundle\ServiceDefinition\Method;
use Bundle\WebServiceBundle\ServiceDefinition\Loader\XmlFileLoader;
/** /**
* UnitTest for \Bundle\WebServiceBundle\SoapKernel. * UnitTest for \Bundle\WebServiceBundle\SoapKernel.
* *
@ -36,17 +39,19 @@ class SoapKernelTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
$serviceDefinition = new ServiceDefinition('api'); $serviceDefinition = new ServiceDefinition('api');
$serviceDefinition->getMethods()->add(new ServiceMethod('math_multiply', 'MathController::multiply')); $serviceDefinitionLoader = new XmlFileLoader(__DIR__ . '/fixtures/api-servicedefinition.xml');
$serviceDefinitionLoader = null;
$serviceDefinitionDumper = new StaticFileDumper(__DIR__ . '/fixtures/api.wsdl'); $serviceDefinitionDumper = new StaticFileDumper(__DIR__ . '/fixtures/api.wsdl');
$requestMessageBinder = new RpcLiteralRequestMessageBinder();
$responseMessageBinder = new RpcLiteralResponseMessageBinder();
$serviceBinder = new ServiceBinder($serviceDefinition, $serviceDefinitionLoader, $serviceDefinitionDumper, $requestMessageBinder, $responseMessageBinder);
$httpKernel = $this->getMock('Symfony\\Component\\HttpKernel\\HttpKernelInterface'); $httpKernel = $this->getMock('Symfony\\Component\\HttpKernel\\HttpKernelInterface');
$httpKernel->expects($this->any()) $httpKernel->expects($this->any())
->method('handle') ->method('handle')
->will($this->returnValue(new SoapResponse(200))); ->will($this->returnValue(new SoapResponse(200)));
$this->soapKernel = new SoapKernel($serviceDefinition, $serviceDefinitionLoader, $serviceDefinitionDumper, $httpKernel); $this->soapKernel = new SoapKernel($serviceBinder, $httpKernel);
} }
public function testHandle() public function testHandle()

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<webservice name="api" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns="http://christiankerl.github.com/WebServiceBundle/servicedefinition/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://christiankerl.github.com/WebServiceBundle/servicedefinition/1.0/ ../../../ServiceDefinition/Loader/schema/servicedefinition-1.0.xsd ">
<method name="math_multiply" controller="">
<argument name="a">
<type xml-type="xs:double" php-type="float"/>
</argument>
<argument name="b">
<type xml-type="xs:double" php-type="float"/>
</argument>
<return>
<type xml-type="xs:double" php-type="float"/>
</return>
</method>
</webservice>