From 31d40380a6fca9e704f630ecd236f543e598d5c7 Mon Sep 17 00:00:00 2001 From: Christian Kerl Date: Thu, 7 Oct 2010 15:16:56 +0200 Subject: [PATCH] added initial support for service definition --- DependencyInjection/WebServiceExtension.php | 20 ++- Resources/config/services.xml | 23 +++- ServiceDefinition/Dumper/DumperInterface.php | 10 ++ ServiceDefinition/Loader/FileLoader.php | 21 ++++ ServiceDefinition/Loader/LoaderInterface.php | 15 +++ ServiceDefinition/Loader/XmlFileLoader.php | 116 ++++++++++++++++++ .../Loader/schema/servicedefinition-1.0.xsd | 81 ++++++++++++ ServiceDefinition/ServiceDefinition.php | 80 ++++++++++++ ServiceDefinition/ServiceHeader.php | 35 ++++++ ServiceDefinition/ServiceMethod.php | 35 ++++++ ServiceDefinition/ServiceType.php | 47 +++++++ Soap/SoapHeader.php | 37 ++++++ Soap/SoapRequest.php | 13 +- Soap/SoapResponse.php | 21 ++-- SoapKernel.php | 66 ++++++++-- Tests/SoapKernelTest.php | 18 ++- Tests/StaticFileDumper.php | 21 ++++ Util/Collection.php | 52 ++++++++ 18 files changed, 677 insertions(+), 34 deletions(-) create mode 100644 ServiceDefinition/Dumper/DumperInterface.php create mode 100644 ServiceDefinition/Loader/FileLoader.php create mode 100644 ServiceDefinition/Loader/LoaderInterface.php create mode 100644 ServiceDefinition/Loader/XmlFileLoader.php create mode 100644 ServiceDefinition/Loader/schema/servicedefinition-1.0.xsd create mode 100644 ServiceDefinition/ServiceDefinition.php create mode 100644 ServiceDefinition/ServiceHeader.php create mode 100644 ServiceDefinition/ServiceMethod.php create mode 100644 ServiceDefinition/ServiceType.php create mode 100644 Soap/SoapHeader.php create mode 100644 Tests/StaticFileDumper.php create mode 100644 Util/Collection.php 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