From 60795fbcbb47f754b4254222cafedbc7f5825cda Mon Sep 17 00:00:00 2001 From: Christian Kerl Date: Thu, 30 Dec 2010 02:18:10 +0100 Subject: [PATCH] added support for 'typemap' and 'classmap' SoapServer option --- Converter/ConverterRepository.php | 62 +++++++++++++++++++++ Converter/TypeConverterInterface.php | 30 ++++++++++ Converter/XopIncludeTypeConverter.php | 59 ++++++++++++++++++++ DependencyInjection/WebServiceExtension.php | 3 +- Resources/config/services.xml | 11 +++- ServiceBinding/ServiceBinder.php | 46 ++++++++++++++- SoapKernel.php | 21 ++++++- Tests/SoapKernelTest.php | 6 +- 8 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 Converter/ConverterRepository.php create mode 100644 Converter/TypeConverterInterface.php create mode 100644 Converter/XopIncludeTypeConverter.php diff --git a/Converter/ConverterRepository.php b/Converter/ConverterRepository.php new file mode 100644 index 0000000..b0b85a5 --- /dev/null +++ b/Converter/ConverterRepository.php @@ -0,0 +1,62 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\Converter; + +/** + * + * @author Christian Kerl + */ +use Bundle\WebServiceBundle\SoapKernel; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +class ConverterRepository +{ + private $typeConverters = array(); + + public function __construct() + { + } + + public function addTypeConverter(TypeConverterInterface $converter) + { + $this->typeConverters[] = $converter; + } + + public function toSoapServerTypemap(SoapKernel $kernel) + { + $result = array(); + + foreach($this->typeConverters as $typeConverter) + { + $result[] = array( + 'type_name' => $typeConverter->getTypeName(), + 'type_ns' => $typeConverter->getTypeNamespace(), + 'from_xml' => function($input) use ($kernel, $typeConverter) { + return $typeConverter->convertXmlToPhp($kernel->getRequest(), $input); + }, + 'to_xml' => function($input) use ($kernel, $typeConverter) { + return $typeConverter->convertPhpToXml($kernel->getResponse(), $input); + } + ); + } + + return $result; + } + + public function registerTypeConverterServices(ContainerInterface $container) + { + foreach($container->findTaggedServiceIds('webservice.converter') as $id => $attributes) + { + $this->addTypeConverter($container->get($id)); + } + } +} diff --git a/Converter/TypeConverterInterface.php b/Converter/TypeConverterInterface.php new file mode 100644 index 0000000..d9453b9 --- /dev/null +++ b/Converter/TypeConverterInterface.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\Converter; + +use Bundle\WebServiceBundle\Soap\SoapRequest; + +use Bundle\WebServiceBundle\Soap\SoapResponse; + +/** + * + * @author Christian Kerl + */ +interface TypeConverterInterface +{ + function getTypeNamespace(); + + function getTypeName(); + + function convertXmlToPhp(SoapRequest $request, $data); + + function convertPhpToXml(SoapResponse $response, $data); +} diff --git a/Converter/XopIncludeTypeConverter.php b/Converter/XopIncludeTypeConverter.php new file mode 100644 index 0000000..385a8ad --- /dev/null +++ b/Converter/XopIncludeTypeConverter.php @@ -0,0 +1,59 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\Converter; + +use Bundle\WebServiceBundle\Soap\SoapRequest; + +use Bundle\WebServiceBundle\Soap\SoapResponse; + +use Bundle\WebServiceBundle\Util\String; + +/** + * + * @author Christian Kerl + */ +class XopIncludeTypeConverter implements TypeConverterInterface +{ + public function getTypeNamespace() + { + return 'http://www.w3.org/2001/XMLSchema'; + } + + public function getTypeName() + { + return 'base64Binary'; + } + + public function convertXmlToPhp(SoapRequest $request, $data) + { + $doc = new \DOMDocument(); + $doc->loadXML($data); + + $includes = $doc->getElementsByTagNameNS('http://www.w3.org/2004/08/xop/include', 'Include'); + $include = $includes->item(0); + + $ref = $include->getAttribute('href'); + + if(String::startsWith($ref, 'cid:')) + { + $cid = urldecode(substr($ref, 4)); + + return $request->getSoapAttachments()->get($cid)->getContent(); + } + + return $data; + } + + public function convertPhpToXml(SoapResponse $response, $data) + { + return $data; + } +} diff --git a/DependencyInjection/WebServiceExtension.php b/DependencyInjection/WebServiceExtension.php index e4b4d9e..17fb3ca 100644 --- a/DependencyInjection/WebServiceExtension.php +++ b/DependencyInjection/WebServiceExtension.php @@ -23,7 +23,7 @@ class WebServiceExtension extends Extension { public function configLoad(array $config, ContainerBuilder $configuration) { - if(!$configuration->hasDefinition('webservice_http_kernel')) + if(!$configuration->hasDefinition('webservice.kernel')) { $loader = new XmlFileLoader($configuration, __DIR__ . '/../Resources/config'); $loader->load('services.xml'); @@ -55,6 +55,7 @@ class WebServiceExtension extends Extension $configuration->setParameter('webservice.definition.name', $config['name']); $configuration->setParameter('webservice.definition.resource', isset($config['resource']) ? $config['resource'] : null); + $configuration->setParameter('webservice.definition.wsdl', isset($config['wsdl']) ? $config['wsdl'] : null); } protected function registerServiceBindingConfig(array $config, ContainerBuilder $configuration) diff --git a/Resources/config/services.xml b/Resources/config/services.xml index accd3c7..e0eb394 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -11,7 +11,7 @@ Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition Bundle\WebServiceBundle\ServiceDefinition\Loader\XmlFileLoader - Bundle\WebServiceBundle\ServiceDefinition\Dumper\Wsdl11DocumentLiteralWrappedFileDumper + Bundle\WebServiceBundle\Tests\StaticFileDumper @@ -23,6 +23,7 @@ + @@ -38,6 +39,12 @@ + + + + + + %webservice.definition.name% @@ -45,7 +52,7 @@ %webservice.definition.resource% - + %webservice.definition.wsdl% \ No newline at end of file diff --git a/ServiceBinding/ServiceBinder.php b/ServiceBinding/ServiceBinder.php index 207cecf..375e2f5 100644 --- a/ServiceBinding/ServiceBinder.php +++ b/ServiceBinding/ServiceBinder.php @@ -10,6 +10,8 @@ namespace Bundle\WebServiceBundle\ServiceBinding; +use Bundle\WebServiceBundle\ServiceDefinition\Type; + use Bundle\WebServiceBundle\Soap\SoapHeader; use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition; @@ -62,6 +64,39 @@ class ServiceBinder return $this->definitionDumper->dumpServiceDefinition($this->definition); } + public function getSoapServerClassmap() + { + $result = array(); + + foreach($this->definition->getHeaders() as $header) + { + $this->addSoapServerClassmapEntry($result, $header->getType()); + } + + foreach($this->definition->getMethods() as $method) + { + foreach($method->getArguments() as $arg) + { + $this->addSoapServerClassmapEntry($result, $arg->getType()); + } + } + + return $result; + } + + private function addSoapServerClassmapEntry(&$classmap, Type $type) + { + list($namespace, $xmlType) = $this->parsePackedQName($type->getXmlType()); + $phpType = $type->getPhpType(); + + if(isset($classmap[$xmlType]) && $classmap[$xmlType] != $phpType) + { + // log warning + } + + $classmap[$xmlType] = $phpType; + } + public function isServiceHeader($name) { return $this->definition->getHeaders()->has($name); @@ -99,11 +134,18 @@ class ServiceBinder protected function createSoapHeader(Header $headerDefinition, $data) { - if(!preg_match('/^\{(.+)\}(.+)$/', $headerDefinition->getType()->getXmlType(), $matches)) + list($namespace, $name) = $this->parsePackedQName($headerDefinition->getType()->getXmlType()); + + return new SoapHeader($namespace, $name, $data); + } + + private function parsePackedQName($qname) + { + if(!preg_match('/^\{(.+)\}(.+)$/', $qname, $matches)) { throw new \InvalidArgumentException(); } - return new SoapHeader($matches[1], $matches[2], $data); + return array($matches[1], $matches[2]); } } \ No newline at end of file diff --git a/SoapKernel.php b/SoapKernel.php index 6e67b1f..e939583 100644 --- a/SoapKernel.php +++ b/SoapKernel.php @@ -10,6 +10,7 @@ namespace Bundle\WebServiceBundle; + use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -21,6 +22,8 @@ use Bundle\WebServiceBundle\Soap\SoapHeader; use Bundle\WebServiceBundle\ServiceBinding\ServiceBinder; +use Bundle\WebServiceBundle\Converter\ConverterRepository; + use Bundle\WebServiceBundle\Util\String; /** @@ -58,11 +61,18 @@ class SoapKernel implements HttpKernelInterface */ protected $kernel; - public function __construct(ServiceBinder $serviceBinder, HttpKernelInterface $kernel) + public function __construct(ServiceBinder $serviceBinder, ConverterRepository $converterRepository, HttpKernelInterface $kernel) { $this->serviceBinder = $serviceBinder; - $this->soapServer = new \SoapServer($this->serviceBinder->getSerializedServiceDefinition()); + $this->soapServer = new \SoapServer( + $this->serviceBinder->getSerializedServiceDefinition(), + array( + 'classmap' => $this->serviceBinder->getSoapServerClassmap(), + 'typemap' => $converterRepository->toSoapServerTypemap($this), + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, + ) + ); $this->soapServer->setObject($this); $this->kernel = $kernel; @@ -73,7 +83,12 @@ class SoapKernel implements HttpKernelInterface return $this->soapRequest; } - public function handle(Request $request = null, $type = self::MASTER_REQUEST, $raw = false) + public function getResponse() + { + return $this->soapResponse; + } + + public function handle(Request $request = null, $type = self::MASTER_REQUEST, $catch = true) { $this->soapRequest = $this->checkRequest($request); diff --git a/Tests/SoapKernelTest.php b/Tests/SoapKernelTest.php index 49e8727..21d4490 100644 --- a/Tests/SoapKernelTest.php +++ b/Tests/SoapKernelTest.php @@ -10,6 +10,8 @@ namespace Bundle\WebServiceBundle\Tests; +use Bundle\WebServiceBundle\Converter\ConverterRepository; + use Symfony\Component\HttpFoundation\Request; use Bundle\WebServiceBundle\SoapKernel; @@ -46,12 +48,14 @@ class SoapKernelTest extends \PHPUnit_Framework_TestCase $serviceBinder = new ServiceBinder($serviceDefinition, $serviceDefinitionLoader, $serviceDefinitionDumper, $requestMessageBinder, $responseMessageBinder); + $converterRepository = new ConverterRepository(); + $httpKernel = $this->getMock('Symfony\\Component\\HttpKernel\\HttpKernelInterface'); $httpKernel->expects($this->any()) ->method('handle') ->will($this->returnValue(new SoapResponse(200))); - $this->soapKernel = new SoapKernel($serviceBinder, $httpKernel); + $this->soapKernel = new SoapKernel($serviceBinder, $converterRepository, $httpKernel); } public function testHandle()