diff --git a/composer.json b/composer.json index d4b435e..3622c0a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ ], "require": { "php": "^7.2", - "rybakit/arguments-resolver": "^0.5.0", + "ext-simplexml": "*", "doctrine/instantiator": "^1.0.3", "goetas-webservices/xsd2php-runtime": "^0.2.11", "jms/serializer": "^3.0" diff --git a/src/Arguments/ArgumentsReader.php b/src/Arguments/ArgumentsReader.php index 48c4eeb..ad1c7c7 100644 --- a/src/Arguments/ArgumentsReader.php +++ b/src/Arguments/ArgumentsReader.php @@ -5,7 +5,6 @@ namespace GoetasWebservices\SoapServices\Metadata\Arguments; use Doctrine\Instantiator\Instantiator; -use GoetasWebservices\SoapServices\Metadata\Arguments\Headers\Header; use GoetasWebservices\SoapServices\Metadata\SerializerUtils; use JMS\Serializer\Accessor\DefaultAccessorStrategy; use JMS\Serializer\DeserializationContext; @@ -30,21 +29,24 @@ public function __construct(Serializer $serializer) */ public function readArguments(array $args, array $message): object { - $envelope = array_filter($args, static function ($item) use ($message) { + $envelopes = array_filter($args, static function ($item) use ($message) { return $item instanceof $message['message_fqcn']; }); - if ($envelope) { - return reset($envelope); + if ($envelopes) { + $envelope = reset($envelopes); + $this->handleHeaders($args, $message, $envelope); + + return $envelope; } $instantiator = new Instantiator(); $envelope = $instantiator->instantiate($message['message_fqcn']); + $this->handleHeaders($args, $message, $envelope); if (!count($message['parts'])) { return $envelope; } - $args = $this->handleHeaders($args, $message, $envelope); if ($args[0] instanceof $message['part_fqcn']) { $envelope->setBody($args[0]); @@ -99,40 +101,15 @@ public function readArguments(array $args, array $message): object /** * @param array $args * @param array $message - * - * @return array */ - private function handleHeaders(array $args, array $message, object $envelope): array + private function handleHeaders(array $args, array $message, object $envelope): void { $headers = array_filter($args, static function ($item) use ($message) { return $item instanceof $message['headers_fqcn']; }); - if ($headers) { + if (count($headers)) { $envelope->setHeader(reset($headers)); - } else { - $headers = array_filter($args, static function ($item) { - return $item instanceof Header; - }); - if (count($headers)) { - $factory = SerializerUtils::getMetadataFactory($this->serializer); - $classMetadata = $factory->getMetadataForClass($message['message_fqcn']); - $propertyMetadata = $classMetadata->propertyMetadata['header']; - - $instantiator = new Instantiator(); - $header = $instantiator->instantiate($propertyMetadata->type['name']); - foreach ($headers as $headerInfo) { - $header->addHeader($headerInfo); - } - - $envelope->setHeader($header); - } } - - $args = array_filter($args, static function ($item) use ($message) { - return !($item instanceof Header) && !($item instanceof $message['headers_fqcn']); - }); - - return $args; } /** diff --git a/src/Arguments/Headers/Handler/FaultHandler.php b/src/Arguments/Headers/Handler/FaultHandler.php deleted file mode 100644 index 21be423..0000000 --- a/src/Arguments/Headers/Handler/FaultHandler.php +++ /dev/null @@ -1,30 +0,0 @@ - GraphNavigator::DIRECTION_DESERIALIZATION, - 'format' => 'xml', - 'type' => 'GoetasWebservices\SoapServices\Metadata\Arguments\Headers\Handler\RawFaultDetail', - 'method' => 'deserializeFaultDetail', - ], - ]; - } - - public function deserializeFaultDetail(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context): \SimpleXMLElement - { - return $data->children(); - } -} diff --git a/src/Arguments/Headers/Handler/HeaderHandler.php b/src/Arguments/Headers/Handler/HeaderHandler.php deleted file mode 100644 index 6082687..0000000 --- a/src/Arguments/Headers/Handler/HeaderHandler.php +++ /dev/null @@ -1,139 +0,0 @@ - GraphNavigator::DIRECTION_SERIALIZATION, - 'format' => 'xml', - 'type' => HeaderPlaceholder::class, - 'method' => 'serializeHeaderPlaceholder', - ], - [ - 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, - 'format' => 'xml', - 'type' => HeaderPlaceholder::class, - 'method' => 'deserializeHeaderPlaceholder', - ], - [ - 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, - 'format' => 'xml', - 'type' => 'GoetasWebservices\SoapServices\SoapEnvelope\Header', - 'method' => 'deserializeHeader', - ], - [ - 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, - 'format' => 'xml', - 'type' => Header::class, - 'method' => 'serializeHeader', - ], - ]; - } - - /** - * @return mixed - */ - public function deserializeHeaderPlaceholder(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context) - { - $type = ['name' => $type['params'][0], 'params' => []]; - - return $context->getNavigator()->accept($data, $type, $context); - } - - /** - * @return mixed - */ - public function deserializeHeader(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context) - { - $type = ['name' => $type['params'][0], 'params' => []]; - - $return = $context->getNavigator()->accept($data, $type, $context); - - $mustUnderstandAttr = $data->attributes(self::SOAP_12)->mustUnderstand ?: $data->attributes(self::SOAP)->mustUnderstand; - $mustUnderstand = null !== $mustUnderstandAttr && $visitor->visitBoolean($mustUnderstandAttr, [], $context); - $headerBag = $context->getAttribute('headers_bag'); - \assert($headerBag instanceof HeaderBag); - - if ($mustUnderstand) { - $headerBag->addMustUnderstandHeader($return); - } else { - $headerBag->addHeader($return); - } - - return $return; - } - - public function serializeHeader(XmlSerializationVisitor $visitor, Header $header, array $type, SerializationContext $context): void - { - $factory = $context->getMetadataFactory(); - - /** - * @var $classMetadata \JMS\Serializer\Metadata\ClassMetadata - */ - $classMetadata = $factory->getMetadataForClass(get_class($header->getData())); - - $name = false !== ($pos = strpos($classMetadata->xmlRootName, ':')) ? substr($classMetadata->xmlRootName, $pos + 1) : $classMetadata->xmlRootName; - - $metadata = new StaticPropertyMetadata($classMetadata->name, $name, $header->getData()); - $metadata->xmlNamespace = $classMetadata->xmlRootNamespace; - $metadata->serializedName = $name; - - $visitor->visitProperty($metadata, $header->getData(), $context); - - $this->handleOptions($visitor, $header->getOptions()); - } - - private function handleOptions(XmlSerializationVisitor $visitor, array $options): void - { - if (!count($options)) { - return; - } - - /** - * @var $currentNode \DOMNode - */ - $currentNode = $visitor->getCurrentNode(); - foreach ($options as $option => $value) { - if (in_array($option, ['mustUnderstand', 'required', 'role', 'actor'])) { - if (self::SOAP_12 === $currentNode->ownerDocument->documentElement->namespaceURI) { - $envelopeNS = self::SOAP_12; - } else { - $envelopeNS = self::SOAP; - } - - $this->setAttributeOnNode($currentNode->lastChild, $option, $value, $envelopeNS); - } - } - } - - /** - * @param mixed $value - */ - private function setAttributeOnNode(\DOMElement $node, string $name, $value, string $namespace): void - { - if (!($prefix = $node->lookupPrefix($namespace)) && !($prefix = $node->ownerDocument->lookupPrefix($namespace))) { - $prefix = 'ns-' . substr(sha1($namespace), 0, 8); - } - - $node->setAttributeNS($namespace, $prefix . ':' . $name, is_bool($value) || null === $value ? ($value ? 'true' : 'false') : $value); - } -} diff --git a/src/Arguments/Headers/Handler/HeaderPlaceholder.php b/src/Arguments/Headers/Handler/HeaderPlaceholder.php deleted file mode 100644 index c347bb9..0000000 --- a/src/Arguments/Headers/Handler/HeaderPlaceholder.php +++ /dev/null @@ -1,20 +0,0 @@ -__headers[] = $header; - } -} diff --git a/src/Arguments/Headers/HeaderBag.php b/src/Arguments/Headers/HeaderBag.php deleted file mode 100644 index 7f7a393..0000000 --- a/src/Arguments/Headers/HeaderBag.php +++ /dev/null @@ -1,60 +0,0 @@ -headers[spl_object_id($header)]); - } - - public function addHeader(object $header): void - { - $this->headers[spl_object_id($header)] = $header; - } - - public function getHeaders(): array - { - return $this->headers; - } - - public function addMustUnderstandHeader(object $header): void - { - $this->mustUnderstandHeaders[spl_object_id($header)] = $header; - $this->headers[spl_object_id($header)] = $header; - } - - public function isMustUnderstandHeader(object $header): bool - { - return $this->hasHeader($header) && isset($this->mustUnderstandHeaders[spl_object_id($header)]); - } - - public function removeMustUnderstandHeader(object $header): void - { - unset($this->mustUnderstandHeaders[spl_object_id($header)]); - } - - /** - * @return object[] - */ - public function getMustUnderstandHeader(): array - { - return $this->mustUnderstandHeaders; - } -} diff --git a/src/Builder/SoapContainerBuilder.php b/src/Builder/SoapContainerBuilder.php new file mode 100644 index 0000000..fcbbf88 --- /dev/null +++ b/src/Builder/SoapContainerBuilder.php @@ -0,0 +1,231 @@ +setConfigFile($configFile); + $this->addCompilerPass(new CleanupPass()); + } + + public function setConfigFile(string $configFile): void + { + $this->configFile = $configFile; + } + + protected function addExtension(ExtensionInterface $extension): void + { + $this->extensions[] = $extension; + } + + protected function addCompilerPass(CompilerPassInterface $pass): void + { + $this->compilerPasses[] = $pass; + } + + public function setContainerClassName(string $fqcn): void + { + $fqcn = strtr($fqcn, [ + '.' => '\\', + '/' => '\\', + ]); + $pos = strrpos($fqcn, '\\'); + $this->className = substr($fqcn, $pos + 1); + $this->classNs = substr($fqcn, 0, $pos); + } + + /** + * @param array $metadata + */ + protected function getContainerBuilder(array $metadata = []): ContainerBuilder + { + $container = new ContainerBuilder(); + + foreach ($this->extensions as $extension) { + $container->registerExtension($extension); + } + + foreach ($this->compilerPasses as $pass) { + $container->addCompilerPass($pass); + } + + $locator = new FileLocator('.'); + $loaders = [ + new YamlFileLoader($container, $locator), + new XmlFileLoader($container, $locator), + ]; + $delegatingLoader = new DelegatingLoader(new LoaderResolver($loaders)); + $delegatingLoader->load($this->configFile); + + // set the production soap metadata + $container->setParameter('goetas_webservices.soap.metadata', $metadata); + + $container->compile(); + + return $container; + } + + /** + * @return array + */ + protected function fetchMetadata(ContainerInterface $debugContainer): array + { + $metadataReader = $debugContainer->get('goetas_webservices.soap.metadata_loader.dev'); + $wsdlMetadata = $debugContainer->getParameter('goetas_webservices.soap.config')['metadata']; + $metadata = []; + foreach (array_keys($wsdlMetadata) as $uri) { + $metadata[$uri] = $metadataReader->load($uri); + } + + return $metadata; + } + + public function getDebugContainer(): ContainerBuilder + { + return $this->getContainerBuilder(); + } + + public function getProdContainer(): ContainerInterface + { + $ref = new \ReflectionClass(sprintf('%s\\%s', $this->classNs, $this->className)); + + return $ref->newInstance(); + } + + public function dumpContainerForProd(string $dir, ContainerInterface $debugContainer): void + { + $metadata = $this->fetchMetadata($debugContainer); + + if (!$metadata) { + throw new \Exception('Empty metadata can not be used for production'); + } + + $forProdContainer = $this->getContainerBuilder($metadata); + $this->dump($forProdContainer, $dir); + } + + private function dump(ContainerBuilder $container, string $dir): void + { + $dumper = new PhpDumper($container); + $options = [ + 'debug' => false, + 'class' => $this->className, + 'namespace' => $this->classNs, + ]; + + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + file_put_contents($dir . '/' . $this->className . '.php', $dumper->dump($options)); + } + + public static function createSerializerBuilderFromContainer(ContainerInterface $container, ?callable $handlersCallback = null, ?callable $listenersCallback = null, ?string $metadataDirPrefix = null): SerializerBuilder + { + $destinations = $container->getParameter('goetas_webservices.soap.config')['destinations_jms']; + + if (null !== $metadataDirPrefix) { + $destinations = array_map(static function ($dir) use ($metadataDirPrefix) { + return rtrim($metadataDirPrefix, '/') . '/' . $dir; + }, $destinations); + } + + return self::createSerializerBuilder($destinations, $handlersCallback, $listenersCallback); + } + + /** + * @param array $jmsMetadata + */ + public static function createSerializerBuilder(array $jmsMetadata, ?callable $handlersCallback = null, ?callable $listenersCallback = null): SerializerBuilder + { + $jmsMetadata = array_merge(self::getMetadataForSoapEnvelope(), $jmsMetadata); + + $serializerBuilder = SerializerBuilder::create(); + + $h = new HeaderHandler(); + $serializerBuilder->configureHandlers(static function (HandlerRegistryInterface $handler) use ($handlersCallback, $serializerBuilder, $h): void { + $serializerBuilder->addDefaultHandlers(); + $handler->registerSubscribingHandler(new BaseTypesHandler()); // XMLSchema List handling + $handler->registerSubscribingHandler(new XmlSchemaDateHandler()); // XMLSchema date handling + $handler->registerSubscribingHandler($h); + if ($handlersCallback) { + call_user_func($handlersCallback, $handler); + } + }); + + $serializerBuilder->configureListeners(static function (EventDispatcherInterface $d) use ($serializerBuilder, $h, $listenersCallback): void { + $serializerBuilder->addDefaultListeners(); + $d->addSubscriber($h); + if ($listenersCallback) { + call_user_func($listenersCallback, $d); + } + }); + + foreach ($jmsMetadata as $php => $dir) { + $serializerBuilder->addMetadataDir($dir, $php); + } + + return $serializerBuilder; + } + + /** + * @return string[] + */ + public static function getMetadataForSoapEnvelope(): array + { + $ref = new \ReflectionClass(Envelope::class); + + return [ + 'GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12' => dirname($ref->getFileName()) . '/../Resources/metadata/jms12', + 'GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope' => dirname($ref->getFileName()) . '/../Resources/metadata/jms', + ]; + } +} diff --git a/src/DependencyInjection/Compiler/CleanupPass.php b/src/DependencyInjection/Compiler/CleanupPass.php new file mode 100644 index 0000000..39f2a2a --- /dev/null +++ b/src/DependencyInjection/Compiler/CleanupPass.php @@ -0,0 +1,25 @@ +getParameter('goetas_webservices.soap.metadata')) { + return; + } + + foreach ($container->getDefinitions() as $id => $definition) { + if (false === strpos($id, 'goetas_webservices.soap.metadata_loader.array') && !$definition->isSynthetic() && $definition->isPublic()) { + $definition->setPublic(false); + } + } + } +} diff --git a/src/Envelope/Envelope.php b/src/Envelope/Envelope.php new file mode 100644 index 0000000..ef7f515 --- /dev/null +++ b/src/Envelope/Envelope.php @@ -0,0 +1,9 @@ +setBody($faultBody); - - $fault = new FaultPart(); - if (!$e instanceof FaultException) { - $e = new ServerException($e->getMessage(), $e->getCode(), $e); - } - - if ($e instanceof ClientException) { - $fault->setCode('SOAP:Client'); - } elseif ($e instanceof VersionMismatchException) { - $fault->setCode('SOAP:VersionMismatch'); - } elseif ($e instanceof MustUnderstandException) { - $fault->setCode('SOAP:MustUnderstand'); - } else { - $fault->setCode('SOAP:Server'); - } - - if ($debug) { - $fault->setString(implode("\n", array_merge([$e->getMessage()], explode("\n", (string) $e)))); - } else { - $fault->setString($e->getMessage()); - } - - // @todo implement detail wrapping - $fault->setDetail($e->getDetail()); - - $faultBody->setFault($fault); - - return $faultEnvelope; - } - - public function createException(ResponseInterface $response, RequestInterface $request, ?\Throwable $e = null): Fault11Exception - { - return new Fault11Exception($this->getBody()->getFault(), $response, $request, $e); - } } diff --git a/src/Envelope/SoapEnvelope12/Messages/Fault.php b/src/Envelope/SoapEnvelope12/Messages/Fault.php index d8fe83b..e9935a0 100644 --- a/src/Envelope/SoapEnvelope12/Messages/Fault.php +++ b/src/Envelope/SoapEnvelope12/Messages/Fault.php @@ -4,18 +4,6 @@ namespace GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12\Messages; -use GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12\Parts\Fault as FaultPart; -use GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12\Parts\FaultCode; -use GoetasWebservices\SoapServices\Metadata\Exception\Fault12Exception; -use GoetasWebservices\SoapServices\Metadata\Exception\FaultException as FaultExceptionMeta; -use GoetasWebservices\SoapServices\SoapServer\Exception\ClientException; -use GoetasWebservices\SoapServices\SoapServer\Exception\FaultException; -use GoetasWebservices\SoapServices\SoapServer\Exception\MustUnderstandException; -use GoetasWebservices\SoapServices\SoapServer\Exception\ServerException; -use GoetasWebservices\SoapServices\SoapServer\Exception\VersionMismatchException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; - /** * Class representing Body */ @@ -43,57 +31,4 @@ public function setBody(FaultBody $body): self return $this; } - - public static function fromException(\Throwable $e, bool $debug = false): self - { - $faultEnvelope = new self(); - $faultBody = new FaultBody(); - $faultEnvelope->setBody($faultBody); - - $fault = new FaultPart(); - if (!$e instanceof FaultException) { - $e = new ServerException($e->getMessage(), $e->getCode(), $e); - } - - $faultCode = new FaultCode(); - - if ($e instanceof VersionMismatchException) { - $faultCode->setValue('SOAP:VersionMismatch'); - } elseif ($e instanceof MustUnderstandException) { - $faultCode->setValue('SOAP:MustUnderstand'); - } elseif ($e instanceof ClientException) { - $faultCode->setValue('SOAP:Sender'); - } else { - $faultCode->setValue('SOAP:Receiver'); - } - - if (0 !== $e->getCode()) { - $subFaultCode = new FaultCode(); - $subFaultCode->setValue((string) $e->getCode()); - - $faultCode->setSubcode($subFaultCode); - } - - $fault->setCode($faultCode); - if ($debug) { - $fault->setReason(array_merge([$e->getMessage()], explode("\n", (string) $e))); - } else { - $fault->setReason(explode("\n", $e->getMessage())); - } - - // @todo implement detail wrapping - $fault->setDetail($e->getDetail()); - - $faultBody->setFault($fault); - - return $faultEnvelope; - } - - /** - * @param \Exception $e - */ - public function createException(ResponseInterface $response, RequestInterface $request, ?\Throwable $e = null): FaultExceptionMeta - { - return new Fault12Exception($this->getBody()->getFault(), $response, $request, $e); - } } diff --git a/src/Headers/Handler/HeaderHandler.php b/src/Headers/Handler/HeaderHandler.php new file mode 100644 index 0000000..cc7fc94 --- /dev/null +++ b/src/Headers/Handler/HeaderHandler.php @@ -0,0 +1,228 @@ + 'serializer.pre_serialize', 'method' => 'ensureHeaderSerialized', 'interface' => Envelope::class], + ]; + } + + public static function getSubscribingMethods(): array + { + return [ + [ + 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, + 'format' => 'xml', + 'type' => 'GoetasWebservices\SoapServices\Metadata\Headers\RawFaultDetail', + 'method' => 'deserializeFaultDetail', + ], + [ + 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, + 'format' => 'xml', + 'type' => 'GoetasWebservices\SoapServices\Metadata\Headers\Handler\HeaderPlaceholder', + 'method' => 'serializeHeaderPlaceholder', + ], + [ + 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, + 'format' => 'xml', + 'type' => 'GoetasWebservices\SoapServices\Metadata\Headers\Handler\HeaderPlaceholder', + 'method' => 'deserializeHeaderPlaceholder', + ], + [ + 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, + 'format' => 'xml', + 'type' => 'GoetasWebservices\SoapServices\SoapEnvelope\Header', + 'method' => 'deserializeHeader', + ], + [ + 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, + 'format' => 'xml', + 'type' => Header::class, + 'method' => 'serializeHeader', + ], + ]; + } + + public function ensureHeaderSerialized(PreSerializeEvent $event): void + { + $envelope = $event->getObject(); + if (!($envelope instanceof Envelope)) { + return; + } + + $context = $event->getContext(); + $headerBag = $context->getAttribute('headers_outgoing'); + \assert($headerBag instanceof HeadersOutgoing); + + if (count($headerBag->getHeaders()) > 0 && !$envelope->getHeader()) { + $factory = $event->getContext()->getMetadataFactory(); + $envelopeMetadata = $factory->getMetadataForClass($event->getType()['name']); + $headerType = $envelopeMetadata->propertyMetadata['header']->type; + $defaultHeaderClass = $headerType['params'][0]; + $envelope->setHeader(new $defaultHeaderClass()); + } + } + + /** + * @return mixed + */ + public function deserializeHeaderPlaceholder(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context) + { + $type = ['name' => $type['params'][0], 'params' => []]; + $headers = $context->getNavigator()->accept($data, $type, $context); + + $headerBag = $context->getAttribute('headers_incoming'); + \assert($headerBag instanceof HeadersIncoming); + + foreach ($data->xpath('./*') as $node) { + if ($this->isMustUnderstand($node, $visitor, $context)) { + $headerBag->addMustUnderstandHeader($node); + } else { + $headerBag->addHeader($node); + } + } + + return $headers; + } + + /** + * @return mixed + */ + public function deserializeHeader(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context) + { + $type = ['name' => $type['params'][0], 'params' => []]; + + $return = $context->getNavigator()->accept($data, $type, $context); + + $headerBag = $context->getAttribute('headers_incoming'); + \assert($headerBag instanceof HeadersIncoming); + + if ($this->isMustUnderstand($data, $visitor, $context)) { + $headerBag->addMustUnderstandHeader($data, $return); + } else { + $headerBag->addHeader($data, $return); + } + + return $return; + } + + private function isMustUnderstand(\SimpleXMLElement $data, XmlDeserializationVisitor $visitor, DeserializationContext $context): bool + { + $domElement = dom_import_simplexml($data); + $mustUnderstandAttr = $domElement->getAttributeNS($domElement->ownerDocument->documentElement->namespaceURI, 'mustUnderstand'); + + return !empty($mustUnderstandAttr) && $visitor->visitBoolean($mustUnderstandAttr, [], $context); + } + + public function serializeHeaderPlaceholder(XmlSerializationVisitor $visitor, object $data, array $type, SerializationContext $context): void + { + // serialize default headers + $context->stopVisiting($data); + $type = ['name' => $type['params'][0], 'params' => []]; + $context->getNavigator()->accept($data, $type, $context); + $context->startVisiting($data); + + // serialize additional headers + $headerBag = $context->getAttribute('headers_outgoing'); + \assert($headerBag instanceof HeadersOutgoing); + + $factory = $context->getMetadataFactory(); + foreach ($headerBag->getHeaders() as $header) { + $classMetadata = $factory->getMetadataForClass(get_class($header->getData())); + + $name = false !== ($pos = strpos($classMetadata->xmlRootName, ':')) ? substr($classMetadata->xmlRootName, $pos + 1) : $classMetadata->xmlRootName; + + $metadata = new StaticPropertyMetadata($classMetadata->name, $name, $header->getData()); + $metadata->xmlNamespace = $classMetadata->xmlRootNamespace; + $metadata->serializedName = $name; + + $visitor->visitProperty($metadata, $header->getData(), $context); + + $this->handleOptions($visitor, $header->getOptions()); + } + } + + public function serializeHeader(XmlSerializationVisitor $visitor, Header $header, array $type, SerializationContext $context): void + { + $data = $header->getData(); + if ($data instanceof \DOMElement) { + $importedNode = $data->ownerDocument !== $visitor->getDocument() + ? $visitor->getDocument()->importNode($data, true) + : $data; + $visitor->getCurrentNode()->appendChild($importedNode); + } else { + $factory = $context->getMetadataFactory(); + /** + * @var $classMetadata \JMS\Serializer\Metadata\ClassMetadata + */ + $classMetadata = $factory->getMetadataForClass(get_class($header->getData())); + + $name = false !== ($pos = strpos($classMetadata->xmlRootName, ':')) ? substr($classMetadata->xmlRootName, $pos + 1) : $classMetadata->xmlRootName; + + $metadata = new StaticPropertyMetadata($classMetadata->name, $name, $header->getData()); + $metadata->xmlNamespace = $classMetadata->xmlRootNamespace; + $metadata->serializedName = $name; + + $visitor->visitProperty($metadata, $data, $context); + + $this->handleOptions($visitor, $header->getOptions()); + } + } + + private function handleOptions(XmlSerializationVisitor $visitor, array $options): void + { + if (!count($options)) { + return; + } + + /** + * @var $currentNode \DOMNode + */ + $currentNode = $visitor->getCurrentNode(); + foreach ($options as $option => $value) { + if (in_array($option, ['mustUnderstand', 'required', 'role', 'actor'])) { + $this->setAttributeOnNode($currentNode->lastChild, $option, $value, $currentNode->ownerDocument->documentElement->namespaceURI); + } + } + } + + /** + * @param mixed $value + */ + private function setAttributeOnNode(\DOMElement $node, string $name, $value, string $namespace): void + { + if (!($prefix = $node->lookupPrefix($namespace)) && !($prefix = $node->ownerDocument->lookupPrefix($namespace))) { + $prefix = 'ns-' . substr(sha1($namespace), 0, 8); + } + + $node->setAttributeNS($namespace, $prefix . ':' . $name, is_bool($value) || null === $value ? ($value ? 'true' : 'false') : $value); + } + + public function deserializeFaultDetail(XmlDeserializationVisitor $visitor, \SimpleXMLElement $data, array $type, DeserializationContext $context): \SimpleXMLElement + { + return $data->children(); + } +} diff --git a/src/Arguments/Headers/Header.php b/src/Headers/Header.php similarity index 89% rename from src/Arguments/Headers/Header.php rename to src/Headers/Header.php index c04913b..bbc7929 100644 --- a/src/Arguments/Headers/Header.php +++ b/src/Headers/Header.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace GoetasWebservices\SoapServices\Metadata\Arguments\Headers; +namespace GoetasWebservices\SoapServices\Metadata\Headers; class Header { diff --git a/src/Headers/HeadersIncoming.php b/src/Headers/HeadersIncoming.php new file mode 100644 index 0000000..0dd6dda --- /dev/null +++ b/src/Headers/HeadersIncoming.php @@ -0,0 +1,92 @@ +findId($header); + $this->headers[$id] = [$header, !empty($this->headers[$id][1])]; + $this->mapping[$id] = $this->mapping[$id] ?? ($mappedObject ? spl_object_id($mappedObject) : null); + } + + public function addMustUnderstandHeader(\SimpleXMLElement $header, ?object $mappedObject = null): void + { + $id = $this->findId($header); + $this->headers[$id] = [$header, true]; + $this->mapping[$id] = $this->mapping[$id] ?? ($mappedObject ? spl_object_id($mappedObject) : null); + } + + /** + * @return \SimpleXMLElement[] + */ + public function headersNotUnderstood(): array + { + $headers = []; + foreach ($this->headers as $id => $header) { + if (!empty($header[1]) && empty($this->understood[$id])) { + $headers[] = $header[0]; + } + } + + return $headers; + } + + /** + * @param object|\SimpleXMLElement $header + */ + public function isMustUnderstandHeader(object $header): bool + { + $id = $this->findId($header); + + return isset($this->headers[$id]) && !empty($this->headers[$id][1]); + } + + /** + * @param object|\SimpleXMLElement $header + */ + public function understoodHeader(object $header): void + { + $id = $this->findId($header); + + $this->understood[$id] = true; + } + + /** + * @param object|\SimpleXMLElement $header + */ + private function findId(object $header): string + { + if ($header instanceof \SimpleXMLElement) { + return md5($header->asXML()); + } + + return array_search(spl_object_id($header), $this->mapping) ?: ''; + } + + /** + * @return \SimpleXMLElement[] + */ + public function getRawHeaders(): array + { + return array_column($this->headers, 0); + } +} diff --git a/src/Headers/HeadersOutgoing.php b/src/Headers/HeadersOutgoing.php new file mode 100644 index 0000000..7648c92 --- /dev/null +++ b/src/Headers/HeadersOutgoing.php @@ -0,0 +1,31 @@ +headers = $headers; + } + + public function addHeader(Header $header): void + { + $this->headers[] = $header; + } + + /** + * @return Header[] + */ + public function getHeaders(): array + { + return $this->headers; + } +} diff --git a/src/Resources/metadata/jms-envelope/Arguments.Headers.Handler.HeaderPlaceholder.yml b/src/Resources/metadata/jms-envelope/Arguments.Headers.Handler.HeaderPlaceholder.yml deleted file mode 100644 index fa78d4f..0000000 --- a/src/Resources/metadata/jms-envelope/Arguments.Headers.Handler.HeaderPlaceholder.yml +++ /dev/null @@ -1,5 +0,0 @@ -GoetasWebservices\SoapServices\Metadata\Arguments\Headers\Handler\HeaderPlaceholder: - properties: - __headers: - inline: true - type: array diff --git a/src/Resources/metadata/jms-envelope/Envelope.Fault.yml b/src/Resources/metadata/jms-envelope/Envelope.Fault.yml deleted file mode 100644 index b6ed72a..0000000 --- a/src/Resources/metadata/jms-envelope/Envelope.Fault.yml +++ /dev/null @@ -1,2 +0,0 @@ -GoetasWebservices\SoapServices\Metadata\Envelope\Fault: - properties: [] diff --git a/src/Resources/metadata/jms/Parts.Fault.yml b/src/Resources/metadata/jms/Parts.Fault.yml index 14617b0..72e4d0d 100644 --- a/src/Resources/metadata/jms/Parts.Fault.yml +++ b/src/Resources/metadata/jms/Parts.Fault.yml @@ -40,4 +40,4 @@ GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope\Parts\Fault: accessor: getter: getRawDetail setter: setRawDetail - type: GoetasWebservices\SoapServices\Metadata\Arguments\Headers\Handler\RawFaultDetail + type: GoetasWebservices\SoapServices\Metadata\Headers\RawFaultDetail diff --git a/src/Resources/metadata/jms12/Parts.Fault.yml b/src/Resources/metadata/jms12/Parts.Fault.yml index 7e59fb3..956133b 100644 --- a/src/Resources/metadata/jms12/Parts.Fault.yml +++ b/src/Resources/metadata/jms12/Parts.Fault.yml @@ -61,4 +61,4 @@ GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12\Parts\Fault: accessor: getter: getRawDetail setter: setRawDetail - type: GoetasWebservices\SoapServices\Metadata\Arguments\Headers\Handler\RawFaultDetail + type: GoetasWebservices\SoapServices\Metadata\Headers\RawFaultDetail diff --git a/src/SerializerUtils.php b/src/SerializerUtils.php index 080fb91..f6d1966 100644 --- a/src/SerializerUtils.php +++ b/src/SerializerUtils.php @@ -4,6 +4,7 @@ namespace GoetasWebservices\SoapServices\Metadata; +use JMS\Serializer\Serializer; use Metadata\MetadataFactory; class SerializerUtils @@ -11,10 +12,9 @@ class SerializerUtils /** * Get metadata factory from serializer, with any JMS Serializer version. */ - public static function getMetadataFactory(object $serializer): MetadataFactory + public static function getMetadataFactory(Serializer $serializer): MetadataFactory { - // JMS Serializer 2.x & 3.x - $reflectionProperty = new \ReflectionProperty(get_class($serializer), 'factory'); + $reflectionProperty = new \ReflectionProperty(Serializer::class, 'factory'); $reflectionProperty->setAccessible(true); return $reflectionProperty->getValue($serializer);