diff --git a/.travis.yml b/.travis.yml index 6458c717..9bc6bd1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,16 @@ language: php php: - - 5.3 - - 5.4 - 5.5 - 5.6 + - 7.0 env: - - SYMFONY_VERSION=2.6.* - - SYMFONY_VERSION="dev-master symfony/debug:~2.7@dev symfony/http-kernel:~2.7@dev" + - SYMFONY_VERSION=2.3.* + - SYMFONY_VERSION=2.7.* before_script: + - phpenv config-add myphp.ini - composer self-update - composer require symfony/framework-bundle:${SYMFONY_VERSION} --no-update - composer update --no-interaction --prefer-source @@ -22,4 +22,4 @@ script: matrix: allow_failures: - - env: SYMFONY_VERSION="dev-master symfony/debug:~2.7@dev symfony/http-kernel:~2.7@dev" + - env: SYMFONY_VERSION=2.3.* diff --git a/README.md b/README.md index e1caac88..2773fc65 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Build SOAP and WSDL based web services +[![Build Status](https://travis-ci.org/phpmike/BeSimpleSoap.svg?branch=0.4)](https://travis-ci.org/phpmike/BeSimpleSoap) + # Components BeSimpleSoap consists of five components ... diff --git a/composer.json b/composer.json index 2bc71ca9..2af7c4cd 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,8 @@ "ext-soap": "*", "ext-curl": "*", "ass/xmlsecurity": "~1.0", - "symfony/framework-bundle": "~2.6", - "symfony/twig-bundle": "~2.6", + "symfony/framework-bundle": "~2.3", + "symfony/twig-bundle": "~2.3", "zendframework/zend-mime": "2.1.*" }, "replace": { diff --git a/myphp.ini b/myphp.ini new file mode 100644 index 00000000..34b215a9 --- /dev/null +++ b/myphp.ini @@ -0,0 +1,2 @@ +date.timezone = "Europe/Paris" +always_populate_raw_post_data = -1 \ No newline at end of file diff --git a/src/BeSimple/SoapBundle/Converter/XopIncludeTypeConverter.php b/src/BeSimple/SoapBundle/Converter/XopIncludeTypeConverter.php index b83e38d9..773e175b 100644 --- a/src/BeSimple/SoapBundle/Converter/XopIncludeTypeConverter.php +++ b/src/BeSimple/SoapBundle/Converter/XopIncludeTypeConverter.php @@ -12,7 +12,7 @@ use BeSimple\SoapBundle\Soap\SoapRequest; use BeSimple\SoapBundle\Soap\SoapResponse; -use BeSimple\SoapBundle\Util\String; +use BeSimple\SoapBundle\Util\BsString; use BeSimple\SoapCommon\Converter\TypeConverterInterface; /** @@ -40,7 +40,7 @@ public function convertXmlToPhp(SoapRequest $request, $data) $ref = $include->getAttribute('href'); - if (String::startsWith($ref, 'cid:')) { + if (BsString::startsWith($ref, 'cid:')) { $cid = urldecode(substr($ref, 4)); return $request->getSoapAttachments()->get($cid)->getContent(); diff --git a/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralRequestMessageBinder.php b/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralRequestMessageBinder.php index 13abf080..c5e13190 100644 --- a/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralRequestMessageBinder.php +++ b/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralRequestMessageBinder.php @@ -49,13 +49,13 @@ public function processMessage(Method $messageDefinition, $message, TypeReposito protected function processType($phpType, $message) { $isArray = false; - $type = $this->typeRepository->getType($phpType); if ($type instanceof ArrayOfType) { $isArray = true; $array = array(); - $type = $this->typeRepository->getType($type->get('item')->getType()); + $phpType = substr($type->getPhpType(), 0, strlen($type->getPhpType()) - 2); + $type = $this->typeRepository->getType($phpType); } // @TODO Fix array reference @@ -78,6 +78,21 @@ protected function processType($phpType, $message) $array = $assocArray; } } + if (is_array($message)) { + foreach ($message as $complexType) { + $array[] = $this->checkComplexType($phpType, $complexType); + } + + // See https://github.com/BeSimple/BeSimpleSoapBundle/issues/29 + if (in_array('BeSimple\SoapCommon\Type\AbstractKeyValue', class_parents($phpType))) { + $assocArray = array(); + foreach ($array as $keyValue) { + $assocArray[$keyValue->getKey()] = $keyValue->getValue(); + } + + $array = $assocArray; + } + } $message = $array; } else { diff --git a/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralResponseMessageBinder.php b/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralResponseMessageBinder.php index b6b4361c..ba2e54f7 100644 --- a/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralResponseMessageBinder.php +++ b/src/BeSimple/SoapBundle/ServiceBinding/RpcLiteralResponseMessageBinder.php @@ -32,7 +32,10 @@ public function processMessage(Method $messageDefinition, $message, TypeReposito { $this->typeRepository = $typeRepository; - return $this->processType($messageDefinition->getOutput()->get('return')->getType(), $message); + $parts = $messageDefinition->getOutput()->all(); + $part = array_shift($parts); + + return $this->processType($part->getType(), $message); } private function processType($phpType, $message) diff --git a/src/BeSimple/SoapBundle/ServiceDefinition/Annotation/ComplexType.php b/src/BeSimple/SoapBundle/ServiceDefinition/Annotation/ComplexType.php index 85072d47..0cf0763f 100644 --- a/src/BeSimple/SoapBundle/ServiceDefinition/Annotation/ComplexType.php +++ b/src/BeSimple/SoapBundle/ServiceDefinition/Annotation/ComplexType.php @@ -18,6 +18,7 @@ class ComplexType extends Configuration private $name; private $value; private $isNillable = false; + private $isAttribute = false; public function getName() { @@ -49,6 +50,26 @@ public function setNillable($isNillable) $this->isNillable = (bool) $isNillable; } + /** + * @return bool + */ + public function isAttribute() + { + return $this->isAttribute; + } + + /** + * @param bool $isAttribute + * + * @return $this + */ + public function setIsAttribute($isAttribute) + { + $this->isAttribute = $isAttribute; + + return $this; + } + public function getAliasName() { return 'complextype'; diff --git a/src/BeSimple/SoapBundle/ServiceDefinition/ComplexType.php b/src/BeSimple/SoapBundle/ServiceDefinition/ComplexType.php index d0ae4647..d165b3c8 100644 --- a/src/BeSimple/SoapBundle/ServiceDefinition/ComplexType.php +++ b/src/BeSimple/SoapBundle/ServiceDefinition/ComplexType.php @@ -20,6 +20,7 @@ class ComplexType private $name; private $value; private $isNillable = false; + private $isAttribute = false; public function getName() { @@ -50,4 +51,24 @@ public function setNillable($isNillable) { $this->isNillable = (bool) $isNillable; } + + /** + * @return bool + */ + public function isAttribute() + { + return $this->isAttribute; + } + + /** + * @param bool $isAttribute + * + * @return $this + */ + public function setIsAttribute($isAttribute) + { + $this->isAttribute = $isAttribute; + + return $this; + } } diff --git a/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationClassLoader.php b/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationClassLoader.php index dcd15002..0903f18b 100644 --- a/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationClassLoader.php +++ b/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationClassLoader.php @@ -96,6 +96,7 @@ public function load($class, $type = null) } $serviceReturn = $annotation->getPhpType(); + $serviceXmlReturn = $annotation->getXmlType(); } } @@ -116,7 +117,11 @@ public function load($class, $type = null) throw new \LogicException(sprintf('@Soap\Result non-existent for "%s".', $method->getName())); } - $serviceMethod->setOutput($this->loadType($serviceReturn)); + if (!isset($serviceXmlReturn) || !$serviceXmlReturn) { + $serviceXmlReturn = 'return'; + } + + $serviceMethod->setOutput($this->loadType($serviceReturn), $serviceXmlReturn); $definition->addMethod($serviceMethod); } @@ -155,7 +160,7 @@ private function loadType($phpType) $loaded = $complexTypeResolver->load($phpType); $complexType = new ComplexType($phpType, isset($loaded['alias']) ? $loaded['alias'] : $phpType); foreach ($loaded['properties'] as $name => $property) { - $complexType->add($name, $this->loadType($property->getValue()), $property->isNillable()); + $complexType->add($name, $this->loadType($property->getValue()), $property->isNillable(), $property->isAttribute()); } $this->typeRepository->addComplexType($complexType); diff --git a/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationComplexTypeLoader.php b/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationComplexTypeLoader.php index 1d3e095e..44089b67 100644 --- a/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationComplexTypeLoader.php +++ b/src/BeSimple/SoapBundle/ServiceDefinition/Loader/AnnotationComplexTypeLoader.php @@ -58,6 +58,7 @@ public function load($class, $type = null) $propertyComplexType = new ComplexType(); $propertyComplexType->setValue($complexType->getValue()); $propertyComplexType->setNillable($complexType->isNillable()); + $propertyComplexType->setIsAttribute($complexType->isAttribute()); $propertyComplexType->setName($property->getName()); $annotations['properties']->add($propertyComplexType); } diff --git a/src/BeSimple/SoapBundle/Util/String.php b/src/BeSimple/SoapBundle/Util/BsString.php similarity index 98% rename from src/BeSimple/SoapBundle/Util/String.php rename to src/BeSimple/SoapBundle/Util/BsString.php index b95d53ee..0aa5d607 100644 --- a/src/BeSimple/SoapBundle/Util/String.php +++ b/src/BeSimple/SoapBundle/Util/BsString.php @@ -15,7 +15,7 @@ * * @author Christian Kerl */ -class String +class BsString { /** * Checks if a string starts with a given string. diff --git a/src/BeSimple/SoapBundle/WebServiceContext.php b/src/BeSimple/SoapBundle/WebServiceContext.php index dce81a76..39bb1a93 100644 --- a/src/BeSimple/SoapBundle/WebServiceContext.php +++ b/src/BeSimple/SoapBundle/WebServiceContext.php @@ -79,7 +79,10 @@ public function getWsdlFile($endpoint = null) } $dumper = new Dumper($definition, array('stylesheet' => $this->options['wsdl_stylesheet'])); - $cache->write($dumper->dump()); + + $wsdl = $dumper->dump(); + + $cache->write($wsdl); } return (string) $cache; diff --git a/src/BeSimple/SoapBundle/composer.json b/src/BeSimple/SoapBundle/composer.json index d9eecb2a..c0a263b3 100644 --- a/src/BeSimple/SoapBundle/composer.json +++ b/src/BeSimple/SoapBundle/composer.json @@ -25,8 +25,8 @@ "besimple/soap-common": "0.3.*", "besimple/soap-wsdl": "0.3.*", "ass/xmlsecurity": "~1.0", - "symfony/framework-bundle": "~2.6", - "symfony/twig-bundle": "~2.6", + "symfony/framework-bundle": "~2.3", + "symfony/twig-bundle": "~2.3", "zendframework/zend-mime": "2.1.*" }, "suggest": { diff --git a/src/BeSimple/SoapCommon/Definition/Message.php b/src/BeSimple/SoapCommon/Definition/Message.php index caa78fe2..506c56d3 100644 --- a/src/BeSimple/SoapCommon/Definition/Message.php +++ b/src/BeSimple/SoapCommon/Definition/Message.php @@ -48,13 +48,13 @@ public function isEmpty() return 0 === count($this->parts) ? true : false; } - public function add($name, $phpType, $nillable = false) + public function add($name, $phpType, $nillable = false, $attribute = false) { if ($phpType instanceof TypeInterface) { $phpType = $phpType->getPhpType(); } - $this->parts[$name] = new Part($name, $phpType, $nillable); + $this->parts[$name] = new Part($name, $phpType, $nillable, $attribute); return $this; } diff --git a/src/BeSimple/SoapCommon/Definition/Method.php b/src/BeSimple/SoapCommon/Definition/Method.php index e79e9d2b..579cf58d 100644 --- a/src/BeSimple/SoapCommon/Definition/Method.php +++ b/src/BeSimple/SoapCommon/Definition/Method.php @@ -63,12 +63,19 @@ public function addHeader($name, $type) public function addInput($name, $type) { + $inName = $this->name; + $this->input = new Message($inName); + $this->input->add($name, $type); } - public function setOutput($type) + public function setOutput($type, $name = 'return') { - $this->output->add('return', $type); + if ('return' !== $name) { + $this->output = new Message($name); + } + + $this->output->add($name, $type); } public function getHeaders() diff --git a/src/BeSimple/SoapCommon/Definition/Part.php b/src/BeSimple/SoapCommon/Definition/Part.php index 317f0464..c384982e 100644 --- a/src/BeSimple/SoapCommon/Definition/Part.php +++ b/src/BeSimple/SoapCommon/Definition/Part.php @@ -20,12 +20,14 @@ class Part protected $name; protected $type; protected $nillable; + protected $attribute; - public function __construct($name, $type, $nillable = false) + public function __construct($name, $type, $nillable = false, $attribute = false) { $this->name = $name; $this->type = $type; $this->setNillable($nillable); + $this->setAttribute($attribute); } public function getName() @@ -52,4 +54,24 @@ public function setNillable($nillable) { $this->nillable = (boolean) $nillable; } + + /** + * @return bool + */ + public function isAttribute() + { + return $this->attribute; + } + + /** + * @param bool $attribute + * + * @return $this + */ + public function setAttribute($attribute) + { + $this->attribute = $attribute; + + return $this; + } } diff --git a/src/BeSimple/SoapCommon/Type/KeyValue/Boolean.php b/src/BeSimple/SoapCommon/Type/KeyValue/BsBoolean.php similarity index 85% rename from src/BeSimple/SoapCommon/Type/KeyValue/Boolean.php rename to src/BeSimple/SoapCommon/Type/KeyValue/BsBoolean.php index bfe684ad..7e81c90b 100644 --- a/src/BeSimple/SoapCommon/Type/KeyValue/Boolean.php +++ b/src/BeSimple/SoapCommon/Type/KeyValue/BsBoolean.php @@ -5,7 +5,7 @@ use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap; use BeSimple\SoapCommon\Type\AbstractKeyValue; -class Boolean extends AbstractKeyValue +class BsBoolean extends AbstractKeyValue { /** * @Soap\ComplexType("boolean") diff --git a/src/BeSimple/SoapCommon/Type/KeyValue/Float.php b/src/BeSimple/SoapCommon/Type/KeyValue/BsFloat.php similarity index 85% rename from src/BeSimple/SoapCommon/Type/KeyValue/Float.php rename to src/BeSimple/SoapCommon/Type/KeyValue/BsFloat.php index 6d286b08..fe63c9ae 100644 --- a/src/BeSimple/SoapCommon/Type/KeyValue/Float.php +++ b/src/BeSimple/SoapCommon/Type/KeyValue/BsFloat.php @@ -5,7 +5,7 @@ use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap; use BeSimple\SoapCommon\Type\AbstractKeyValue; -class Float extends AbstractKeyValue +class BsFloat extends AbstractKeyValue { /** * @Soap\ComplexType("float") diff --git a/src/BeSimple/SoapCommon/Type/KeyValue/Int.php b/src/BeSimple/SoapCommon/Type/KeyValue/BsInt.php similarity index 86% rename from src/BeSimple/SoapCommon/Type/KeyValue/Int.php rename to src/BeSimple/SoapCommon/Type/KeyValue/BsInt.php index 0e0c190c..369423c0 100644 --- a/src/BeSimple/SoapCommon/Type/KeyValue/Int.php +++ b/src/BeSimple/SoapCommon/Type/KeyValue/BsInt.php @@ -5,7 +5,7 @@ use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap; use BeSimple\SoapCommon\Type\AbstractKeyValue; -class Int extends AbstractKeyValue +class BsInt extends AbstractKeyValue { /** * @Soap\ComplexType("int") diff --git a/src/BeSimple/SoapCommon/Type/KeyValue/String.php b/src/BeSimple/SoapCommon/Type/KeyValue/BsString.php similarity index 85% rename from src/BeSimple/SoapCommon/Type/KeyValue/String.php rename to src/BeSimple/SoapCommon/Type/KeyValue/BsString.php index 29473a95..7b11b9a5 100644 --- a/src/BeSimple/SoapCommon/Type/KeyValue/String.php +++ b/src/BeSimple/SoapCommon/Type/KeyValue/BsString.php @@ -5,7 +5,7 @@ use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap; use BeSimple\SoapCommon\Type\AbstractKeyValue; -class String extends AbstractKeyValue +class BsString extends AbstractKeyValue { /** * @Soap\ComplexType("string") diff --git a/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php index 15b0d259..40505b27 100644 --- a/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php +++ b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php @@ -30,7 +30,7 @@ abstract class WsSecurityFilterClientServer /** * The date format to be used with {@link \DateTime} */ - const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; + const DATETIME_FORMAT = 'Y-m-d\TH:i:s.u\Z'; /** * (X509 3.2.1) Reference to a Subject Key Identifier diff --git a/src/BeSimple/SoapServer/WsSecurityFilter.php b/src/BeSimple/SoapServer/WsSecurityFilter.php index b2e86a04..234283a0 100644 --- a/src/BeSimple/SoapServer/WsSecurityFilter.php +++ b/src/BeSimple/SoapServer/WsSecurityFilter.php @@ -86,7 +86,7 @@ public function filterRequest(CommonSoapRequest $request) $expires = $xpath->query($query, $security)->item(0); if (null !== $expires) { - $expiresDatetime = \DateTime::createFromFormat(self::DATETIME_FORMAT, $expires->textContent, new \DateTimeZone('UTC')); + $expiresDatetime = \DateTime::createFromFormat(static::DATETIME_FORMAT, $expires->textContent, new \DateTimeZone('UTC')); $currentDatetime = new \DateTime('now', new \DateTimeZone('UTC')); if ($currentDatetime > $expiresDatetime) { @@ -170,7 +170,7 @@ public function filterResponse(CommonSoapResponse $response) // init timestamp $dt = new \DateTime('now', new \DateTimeZone('UTC')); - $createdTimestamp = $dt->format(self::DATETIME_FORMAT); + $createdTimestamp = $dt->format(static::DATETIME_FORMAT); // create security header $security = $filterHelper->createElement(Helper::NS_WSS, 'Security'); @@ -182,7 +182,7 @@ public function filterResponse(CommonSoapResponse $response) $timestamp->appendChild($created); if (null !== $this->expires) { $dt->modify('+' . $this->expires . ' seconds'); - $expiresTimestamp = $dt->format(self::DATETIME_FORMAT); + $expiresTimestamp = $dt->format(static::DATETIME_FORMAT); $expires = $filterHelper->createElement(Helper::NS_WSU, 'Expires', $expiresTimestamp); $timestamp->appendChild($expires); } diff --git a/src/BeSimple/SoapWsdl/Dumper/Dumper.php b/src/BeSimple/SoapWsdl/Dumper/Dumper.php index ef555209..758b0f69 100644 --- a/src/BeSimple/SoapWsdl/Dumper/Dumper.php +++ b/src/BeSimple/SoapWsdl/Dumper/Dumper.php @@ -217,11 +217,13 @@ protected function addMessages(array $messages) protected function addComplexTypes() { - $types = $this->document->createElement('types'); + $types = $this->document->createElement(static::WSDL_NS . ':types'); $this->domDefinitions->appendChild($types); $this->domSchema = $this->document->createElement(static::XSD_NS.':schema'); $this->domSchema->setAttribute('targetNamespace', $this->definition->getNamespace()); + $this->domSchema->setAttribute('elementFormDefault', 'unqualified'); + $this->domSchema->setAttribute(static::XML_NS.':'.static::XSD_NS, static::XSD_NS_URI); $types->appendChild($this->domSchema); foreach ($this->definition->getTypeRepository()->getComplexTypes() as $type) { @@ -236,13 +238,19 @@ protected function addComplexType(ComplexType $type) $complexType = $this->document->createElement(static::XSD_NS.':complexType'); $complexType->setAttribute('name', $type->getXmlType()); - $all = $this->document->createElement(static::XSD_NS.':'.($type instanceof ArrayOfType ? 'sequence' : 'all')); + $all = $this->document->createElement(static::XSD_NS.':'.'sequence'); $complexType->appendChild($all); foreach ($type->all() as $child) { + $isArray = false; $childType = $this->definition->getTypeRepository()->getType($child->getType()); - $element = $this->document->createElement(static::XSD_NS.':element'); + if ($child->isAttribute()) { + $element = $this->document->createElement(static::XSD_NS.':attribute'); + } else { + $element = $this->document->createElement(static::XSD_NS.':element'); + } + $element->setAttribute('name', $child->getName()); if ($childType instanceof ComplexType) { @@ -251,6 +259,11 @@ protected function addComplexType(ComplexType $type) $name = $childType->getName(); } + if (0 === strpos($name, 'ArrayOf')) { + $isArray = true; + $name = lcfirst(substr($name, 7)); + } + $element->setAttribute('type', static::TYPES_NS.':'.$name); } else { $element->setAttribute('type', $childType); @@ -260,12 +273,16 @@ protected function addComplexType(ComplexType $type) $element->setAttribute('nillable', 'true'); } - if ($type instanceof ArrayOfType) { + if ($type instanceof ArrayOfType || $isArray) { $element->setAttribute('minOccurs', 0); $element->setAttribute('maxOccurs', 'unbounded'); } - $all->appendChild($element); + if ($child->isAttribute()) { + $complexType->appendChild($element); + } else { + $all->appendChild($element); + } } $this->domSchema->appendChild($complexType);