diff --git a/DependencyInjection/WebServiceExtension.php b/DependencyInjection/WebServiceExtension.php
index 872e3b2..015591c 100644
--- a/DependencyInjection/WebServiceExtension.php
+++ b/DependencyInjection/WebServiceExtension.php
@@ -28,10 +28,26 @@ class WebServiceExtension extends Extension
$loader = new XmlFileLoader($configuration, __DIR__ . '/../Resources/config');
$loader->load('services.xml');
- $configuration->setAlias('http_kernel', 'webservice_http_kernel');
+ $configuration->setAlias('http_kernel', 'webservice.kernel');
}
- $configuration->setParameter('webservice.config.wsdl', $config['wsdl']);
+ if(!isset($config['definition']))
+ {
+ throw new \InvalidArgumentException();
+ }
+
+ $this->registerServiceDefinitionConfig($config['definition'], $configuration);
+ }
+
+ protected function registerServiceDefinitionConfig(array $config, ContainerBuilder $configuration)
+ {
+ if(!isset($config['name']))
+ {
+ throw new \InvalidArgumentException();
+ }
+
+ $configuration->setParameter('webservice.definition.name', $config['name']);
+ $configuration->setParameter('webservice.definition.resource', isset($config['resource']) ? $config['resource'] : null);
}
public function getXsdValidationBasePath()
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index 6845e72..896ec68 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -5,6 +5,10 @@
Bundle\WebServiceBundle\Soap\SoapRequest
+
+ Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition
+ Bundle\WebServiceBundle\ServiceDefinition\Loader\XmlFileLoader
+ Bundle\WebServiceBundle\ServiceDefinition\Dumper\Wsdl11DocumentLiteralFileDumper
@@ -14,12 +18,21 @@
-
- %webservice.config.wsdl%
-
-
-
+
+
+
+
+
+
+ %webservice.definition.name%
+
+
+ %webservice.definition.resource%
+
+
+
+
\ No newline at end of file
diff --git a/ServiceDefinition/Dumper/DumperInterface.php b/ServiceDefinition/Dumper/DumperInterface.php
new file mode 100644
index 0000000..9cb12ee
--- /dev/null
+++ b/ServiceDefinition/Dumper/DumperInterface.php
@@ -0,0 +1,10 @@
+file = $file;
+ }
+}
\ No newline at end of file
diff --git a/ServiceDefinition/Loader/LoaderInterface.php b/ServiceDefinition/Loader/LoaderInterface.php
new file mode 100644
index 0000000..d3e2a2e
--- /dev/null
+++ b/ServiceDefinition/Loader/LoaderInterface.php
@@ -0,0 +1,15 @@
+parseFile($this->file);
+
+ if($definition->getName() != $xml['name'])
+ {
+ throw new \InvalidArgumentException();
+ }
+
+ foreach($xml->header as $header)
+ {
+ $definition->getHeaders()->add($this->parseHeader($header));
+ }
+
+ foreach($xml->method as $method)
+ {
+ $definition->getMethods()->add($this->parseMethod($method));
+ }
+ }
+
+ /**
+ * @param \SimpleXMLElement $node
+ *
+ * @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceHeader
+ */
+ protected function parseHeader(\SimpleXMLElement $node)
+ {
+ $header = new ServiceHeader((string)$node['name'], $this->parseType($node->type));
+
+ return $header;
+ }
+
+ /**
+ * @param \SimpleXMLElement $node
+ *
+ * @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceMethod
+ */
+ protected function parseMethod(\SimpleXMLElement $node)
+ {
+ $method = new ServiceMethod((string)$node['name'], (string)$node['controller']);
+
+ return $method;
+ }
+
+ /**
+ * @param \SimpleXMLElement $node
+ *
+ * @return \Bundle\WebServiceBundle\ServiceDefinition\ServiceType
+ */
+ protected function parseType(\SimpleXMLElement $node)
+ {
+ $namespaces = $node->getDocNamespaces(true);
+ $qname = explode(':', $node['xml-type'], 2);
+ $xmlType = sprintf('{%s}%s', $namespaces[$qname[0]], $qname[1]);
+
+ $type = new ServiceType((string)$node['php-type'], $xmlType, (string)$node['converter']);
+
+ return $type;
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return \SimpleXMLElement
+ */
+ protected function parseFile($file)
+ {
+ $dom = new \DOMDocument();
+ libxml_use_internal_errors(true);
+ if (!$dom->load($file, LIBXML_COMPACT)) {
+ throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
+ }
+ if (!$dom->schemaValidate(__DIR__.'/schema/servicedefinition-1.0.xsd')) {
+ throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
+ }
+ $dom->validateOnParse = true;
+ $dom->normalizeDocument();
+ libxml_use_internal_errors(false);
+
+ return simplexml_import_dom($dom);
+ }
+
+ protected function getXmlErrors()
+ {
+ $errors = array();
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ? $error->file : 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors(false);
+
+ return $errors;
+ }
+}
\ No newline at end of file
diff --git a/ServiceDefinition/Loader/schema/servicedefinition-1.0.xsd b/ServiceDefinition/Loader/schema/servicedefinition-1.0.xsd
new file mode 100644
index 0000000..ed2d68f
--- /dev/null
+++ b/ServiceDefinition/Loader/schema/servicedefinition-1.0.xsd
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ServiceDefinition/ServiceDefinition.php b/ServiceDefinition/ServiceDefinition.php
new file mode 100644
index 0000000..1446275
--- /dev/null
+++ b/ServiceDefinition/ServiceDefinition.php
@@ -0,0 +1,80 @@
+setName($name);
+ $this->setMethods($methods);
+ $this->setHeaders($headers);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return \Bundle\WebServiceBundle\Util\Collection
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * @param array $methods
+ */
+ public function setMethods($methods)
+ {
+ $this->methods = new Collection('getName');
+ $this->methods->addAll($methods);
+ }
+
+ /**
+ * @return \Bundle\WebServiceBundle\Util\Collection
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * @param array $headers
+ */
+ public function setHeaders($headers)
+ {
+ $this->headers = new Collection('getName');
+ $this->headers->addAll($headers);
+ }
+}
\ No newline at end of file
diff --git a/ServiceDefinition/ServiceHeader.php b/ServiceDefinition/ServiceHeader.php
new file mode 100644
index 0000000..8df73fb
--- /dev/null
+++ b/ServiceDefinition/ServiceHeader.php
@@ -0,0 +1,35 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/ServiceDefinition/ServiceMethod.php b/ServiceDefinition/ServiceMethod.php
new file mode 100644
index 0000000..d78ead6
--- /dev/null
+++ b/ServiceDefinition/ServiceMethod.php
@@ -0,0 +1,35 @@
+setName($name);
+ $this->setController($controller);
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getController()
+ {
+ return $this->controller;
+ }
+
+ public function setController($controller)
+ {
+ $this->controller = $controller;
+ }
+}
\ No newline at end of file
diff --git a/ServiceDefinition/ServiceType.php b/ServiceDefinition/ServiceType.php
new file mode 100644
index 0000000..bcf0699
--- /dev/null
+++ b/ServiceDefinition/ServiceType.php
@@ -0,0 +1,47 @@
+setPhpType($phpType);
+ $this->setXmlType($xmlType);
+ $this->setConverter($converter);
+ }
+
+ public function getPhpType()
+ {
+ return $this->phpType;
+ }
+
+ public function setPhpType($value)
+ {
+ $this->phpType = $value;
+ }
+
+ public function getXmlType()
+ {
+ return $this->xmlType;
+ }
+
+ public function setXmlType($value)
+ {
+ $this->xmlType = $value;
+ }
+
+ public function getConverter()
+ {
+ return $this->converter;
+ }
+
+ public function setConverter($value)
+ {
+ $this->converter = $value;
+ }
+}
\ No newline at end of file
diff --git a/Soap/SoapHeader.php b/Soap/SoapHeader.php
new file mode 100644
index 0000000..5cf895b
--- /dev/null
+++ b/Soap/SoapHeader.php
@@ -0,0 +1,37 @@
+namespace = $namespace;
+ $this->name = $name;
+ $this->data = $data;
+ }
+
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function toNativeSoapHeader()
+ {
+ return new \SoapHeader($this->namespace, $this->name, $this->data);
+ }
+}
\ No newline at end of file
diff --git a/Soap/SoapRequest.php b/Soap/SoapRequest.php
index 7c6b29e..da0d99b 100644
--- a/Soap/SoapRequest.php
+++ b/Soap/SoapRequest.php
@@ -10,6 +10,8 @@
namespace Bundle\WebServiceBundle\Soap;
+use Bundle\WebServiceBundle\Util\Collection;
+
use Symfony\Component\HttpFoundation\Request;
/**
@@ -30,9 +32,9 @@ class SoapRequest extends Request
protected $soapAction;
/**
- * @var unknown
+ * @var \Bundle\WebServiceBundle\Util\Collection
*/
- protected $soapHeader;
+ protected $soapHeaders;
/**
* @var unknown
@@ -44,6 +46,8 @@ class SoapRequest extends Request
parent::__construct($query, null, $attributes, $cookies, null, $server);
$this->rawContent = $rawContent != null ? $rawContent : $this->loadRawContent();
+
+ $this->soapHeaders = new Collection('getName');
}
/**
@@ -56,6 +60,11 @@ class SoapRequest extends Request
return $this->rawContent;
}
+ public function getSoapHeaders()
+ {
+ return $this->soapHeaders;
+ }
+
/**
* Loads the plain HTTP POST data.
*
diff --git a/Soap/SoapResponse.php b/Soap/SoapResponse.php
index 3ef872d..4d7cc34 100644
--- a/Soap/SoapResponse.php
+++ b/Soap/SoapResponse.php
@@ -10,6 +10,8 @@
namespace Bundle\WebServiceBundle\Soap;
+use Bundle\WebServiceBundle\Util\Collection;
+
use Symfony\Component\HttpFoundation\Response;
/**
@@ -19,21 +21,19 @@ use Symfony\Component\HttpFoundation\Response;
*/
class SoapResponse extends Response
{
+ /**
+ * @var \Bundle\WebServiceBundle\Util\Collection
+ */
protected $soapHeaders;
protected $soapReturnValue;
- public function __construct($returnValue)
+ public function __construct($returnValue = null)
{
parent::__construct();
- $this->soapHeaders = array();
- $this->soapReturnValue = $returnValue;
- }
-
- public function addSoapHeader(\SoapHeader $header)
- {
- $this->soapHeaders[] = $header;
+ $this->soapHeaders = new Collection('getName');
+ $this->setReturnValue($returnValue);
}
public function getSoapHeaders()
@@ -41,6 +41,11 @@ class SoapResponse extends Response
return $this->soapHeaders;
}
+ public function setReturnValue($value)
+ {
+ $this->soapReturnValue = $value;
+ }
+
public function getReturnValue()
{
return $this->soapReturnValue;
diff --git a/SoapKernel.php b/SoapKernel.php
index 4441d31..36b1514 100644
--- a/SoapKernel.php
+++ b/SoapKernel.php
@@ -10,6 +10,12 @@
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\Response;
@@ -18,6 +24,9 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
use Bundle\WebServiceBundle\Soap\SoapRequest;
use Bundle\WebServiceBundle\Soap\SoapResponse;
+use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
+use Bundle\WebServiceBundle\ServiceDefinition\Loader\LoaderInterface;
+
use Bundle\WebServiceBundle\Util\String;
/**
@@ -45,14 +54,25 @@ class SoapKernel implements HttpKernelInterface
*/
protected $soapResponse;
+ /**
+ * @var \Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition
+ */
+ protected $serviceDefinition;
+
/**
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $kernel;
- public function __construct(\SoapServer $server, HttpKernelInterface $kernel)
+ public function __construct(ServiceDefinition $definition, LoaderInterface $loader, DumperInterface $dumper, HttpKernelInterface $kernel)
{
- $this->soapServer = $server;
+ $this->serviceDefinition = $definition;
+ $loader->loadServiceDefinition($this->serviceDefinition);
+
+ // assume $dumper creates WSDL 1.1 file
+ $wsdl = $dumper->dumpServiceDefinition($this->serviceDefinition);
+
+ $this->soapServer = new \SoapServer($wsdl);
$this->soapServer->setObject($this);
$this->kernel = $kernel;
@@ -76,26 +96,43 @@ class SoapKernel implements HttpKernelInterface
return $this->soapResponse;
}
+ /**
+ * This method gets called once for every SOAP header the \SoapServer received
+ * and afterwards once for the called SOAP operation.
+ *
+ * @param string $method The SOAP header or SOAP operation name
+ * @param array $arguments
+ *
+ * @return mixed
+ */
public function __call($method, $arguments)
{
- if($this->isSoapHeaderCallback($method))
+ if($this->serviceDefinition->getHeaders()->has($method))
{
- // $this->soapRequest->addSoapHeader(null);
- }
- else
- {
- // TODO: set _controller attribute of request
- $this->soapRequest->attributes->set('_controller', $method);
+ // collect request soap headers
+ $headerDefinition = $this->serviceDefinition->getHeaders()->get($method);
+ $this->soapRequest->getSoapHeaders()->add($this->createSoapHeader($headerDefinition, $arguments[0]));
+ return;
+ }
+
+ if($this->serviceDefinition->getMethods()->has($method))
+ {
+ $methodDefinition = $this->serviceDefinition->getMethods()->get($method);
+ $this->soapRequest->attributes->set('_controller', $methodDefinition->getController());
+
+ // delegate to standard http kernel
$response = $this->kernel->handle($this->soapRequest, self::MASTER_REQUEST, true);
$this->soapResponse = $this->checkResponse($response);
+ // add response soap headers to soap server
foreach($this->soapResponse->getSoapHeaders() as $header)
{
- $this->soapServer->addSoapHeader($header);
+ $this->soapServer->addSoapHeader($header->toNativeSoapHeader());
}
+ // return operation return value to soap server
return $this->soapResponse->getReturnValue();
}
}
@@ -144,8 +181,13 @@ class SoapKernel implements HttpKernelInterface
return $response;
}
- protected function isSoapHeaderCallback($method)
+ protected function createSoapHeader(ServiceHeader $headerDefinition, $data)
{
- return false; //String::endsWith($method, 'Header');
+ if(!preg_match('/^\{(.+)\}(.+)$/', $headerDefinition->getType()->getXmlType(), $matches))
+ {
+ throw new \InvalidArgumentException();
+ }
+
+ return new SoapHeader($matches[1], $matches[2], $data);
}
}
\ No newline at end of file
diff --git a/Tests/SoapKernelTest.php b/Tests/SoapKernelTest.php
index 79887b0..890259f 100644
--- a/Tests/SoapKernelTest.php
+++ b/Tests/SoapKernelTest.php
@@ -11,6 +11,10 @@
namespace Bundle\WebServiceBundle\Tests;
+use Bundle\WebServiceBundle\ServiceDefinition\ServiceMethod;
+
+use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition;
+
use Symfony\Component\HttpFoundation\Request;
use Bundle\WebServiceBundle\SoapKernel;
@@ -31,13 +35,18 @@ class SoapKernelTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
- $soapServer = new \SoapServer(__DIR__ . '/fixtures/api.wsdl');
+ $serviceDefinition = new ServiceDefinition('api');
+ $serviceDefinition->getMethods()->add(new ServiceMethod('math_multiply', 'MathController::multiply'));
+
+ $serviceDefinitionLoader = null;
+ $serviceDefinitionDumper = new StaticFileDumper(__DIR__ . '/fixtures/api.wsdl');
+
$httpKernel = $this->getMock('Symfony\\Component\\HttpKernel\\HttpKernelInterface');
$httpKernel->expects($this->any())
->method('handle')
->will($this->returnValue(new SoapResponse(200)));
- $this->soapKernel = new SoapKernel($soapServer, $httpKernel);
+ $this->soapKernel = new SoapKernel($serviceDefinition, $serviceDefinitionLoader, $serviceDefinitionDumper, $httpKernel);
}
public function testHandle()
@@ -48,11 +57,10 @@ class SoapKernelTest extends \PHPUnit_Framework_TestCase
$this->assertXmlStringEqualsXmlString(self::$soapResponseContent, $response->getContent());
}
- /**
- * @expectedException InvalidArgumentException
- */
public function testHandleWithInvalidRequest()
{
+ $this->setExpectedException('InvalidArgumentException');
+
$this->soapKernel->handle(new Request());
}
}
\ No newline at end of file
diff --git a/Tests/StaticFileDumper.php b/Tests/StaticFileDumper.php
new file mode 100644
index 0000000..dd28739
--- /dev/null
+++ b/Tests/StaticFileDumper.php
@@ -0,0 +1,21 @@
+file = $file;
+ }
+
+ public function dumpServiceDefinition(ServiceDefinition $definition)
+ {
+ return $this->file;
+ }
+}
\ No newline at end of file
diff --git a/Util/Collection.php b/Util/Collection.php
new file mode 100644
index 0000000..2ed42cb
--- /dev/null
+++ b/Util/Collection.php
@@ -0,0 +1,52 @@
+keyPropertyGetter = $keyPropertyGetter;
+ }
+
+ public function add($element)
+ {
+ $this->elements[call_user_func(array($element, $this->keyPropertyGetter))] = $element;
+ }
+
+ public function addAll($elements)
+ {
+ foreach ($elements as $element)
+ {
+ $this->add($element);
+ }
+ }
+
+ public function has($key)
+ {
+ return isset($this->elements[$key]);
+ }
+
+ public function get($key)
+ {
+ return $this->has($key) ? $this->elements[$key] : null;
+ }
+
+ public function clear()
+ {
+ $this->elements = array();
+ }
+
+ public function count()
+ {
+ return count($this->elements);
+ }
+
+ public function getIterator ()
+ {
+ return new \ArrayIterator($this->elements);
+ }
+}
\ No newline at end of file