From f4982769cf99a8831c885b4100ab11062745519b Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Wed, 12 Jun 2024 13:21:36 +0200 Subject: [PATCH 1/5] Limit additional method call --- src/Restriction/WhitespaceRestriction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Restriction/WhitespaceRestriction.php b/src/Restriction/WhitespaceRestriction.php index 007ca17..ec7635f 100644 --- a/src/Restriction/WhitespaceRestriction.php +++ b/src/Restriction/WhitespaceRestriction.php @@ -24,7 +24,7 @@ public static function parseForContext(Context $context, string $value): string return match ($whitespace) { self::REPLACE => self::replace($value), self::COLLAPSE => self::collapse($value), - default => self::preserve($value), + default => $value, }; } From e695287eef84955af94414b138a7ff7c67fa1513 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Wed, 12 Jun 2024 14:16:58 +0200 Subject: [PATCH 2/5] More performant ObjectEncoder --- src/Encoder/ObjectEncoder.php | 99 ++++++++++++++--------------------- 1 file changed, 40 insertions(+), 59 deletions(-) diff --git a/src/Encoder/ObjectEncoder.php b/src/Encoder/ObjectEncoder.php index e0bdce8..36ea4b5 100644 --- a/src/Encoder/ObjectEncoder.php +++ b/src/Encoder/ObjectEncoder.php @@ -4,6 +4,7 @@ namespace Soap\Encoding\Encoder; use Closure; +use Exception; use Soap\Encoding\Normalizer\PhpPropertyNameNormalizer; use Soap\Encoding\TypeInference\ComplexTypeBuilder; use Soap\Encoding\TypeInference\XsiTypeDetector; @@ -13,7 +14,7 @@ use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter; use Soap\Encoding\Xml\Writer\XsiAttributeBuilder; use Soap\Engine\Metadata\Model\Property; -use Soap\Engine\Metadata\Model\XsdType; +use Soap\Engine\Metadata\Model\TypeMeta; use VeeWee\Reflecta\Iso\Iso; use VeeWee\Reflecta\Lens\Lens; use function is_array; @@ -100,30 +101,27 @@ private function to(Context $context, array $properties, object|array $data): st $properties, function (Property $property) use ($context, $data, $defaultAction) : Closure { $type = $property->getType(); - $lens = $this->decorateLensForType( + $meta = $type->getMeta(); + $isAttribute = $meta->isAttribute()->unwrapOr(false); + + /** @var mixed $value */ + $value = $this->runLens( property(PhpPropertyNameNormalizer::normalize($property->getName())), - $type + $meta, + $data, + null ); - /** - * @psalm-var mixed $value - * @psalm-suppress PossiblyInvalidArgument - Psalm gets lost in the lens. - */ - $value = $lens - ->tryGet($data) - ->catch(static fn () => null) - ->getResult(); - - return $this->handleProperty( - $property, - onAttribute: fn (): Closure => $value ? (new AttributeBuilder( + + return match(true) { + $isAttribute => $value ? (new AttributeBuilder( $type, $this->grabIsoForProperty($context, $property)->to($value) ))(...) : $defaultAction, - onValue: fn (): Closure => $value + $property->getName() === '_' => $value ? buildValue($this->grabIsoForProperty($context, $property)->to($value)) : (new NilAttributeBuilder())(...), - onElements: fn (): Closure => $value ? raw($this->grabIsoForProperty($context, $property)->to($value)) : $defaultAction, - ); + default => $value ? raw($this->grabIsoForProperty($context, $property)->to($value)) : $defaultAction + }; } ) ] @@ -149,23 +147,21 @@ private function from(Context $context, array $properties, string $data): object function (Property $property) use ($context, $nodes): mixed { $type = $property->getType(); $meta = $type->getMeta(); - $isList = $meta->isList()->unwrapOr(false); - /** @psalm-var string|null $value */ - $value = $this->decorateLensForType( + + /** @var string|null $value */ + $value = $this->runLens( index($property->getName()), - $type - ) - ->tryGet($nodes) - ->catch(static fn () => null) - ->getResult(); - $defaultValue = $isList ? [] : null; - - return $this->handleProperty( - $property, - onAttribute: fn (): mixed => /** @psalm-suppress PossiblyNullArgument */$this->grabIsoForProperty($context, $property)->from($value), - onValue: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue, - onElements: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue, + $meta, + $nodes, + null ); + $defaultValue = $meta->isList()->unwrapOr(false) ? [] : null; + + /** @psalm-suppress PossiblyNullArgument */ + return match(true) { + $meta->isAttribute()->unwrapOr(false) => $this->grabIsoForProperty($context, $property)->from($value), + default => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue, + }; }, static fn (Property $property) => PhpPropertyNameNormalizer::normalize($property->getName()), ) @@ -183,27 +179,14 @@ private function grabIsoForProperty(Context $context, Property $property): Iso return $encoder->iso($propertyContext); } - /** - * @template X - * - * @param Closure(): X $onAttribute - * @param Closure(): X $onValue - * @param Closure(): X $onElements - * @return X - */ - private function handleProperty( - Property $property, - Closure $onAttribute, - Closure $onValue, - Closure $onElements, - ) { - $meta = $property->getType()->getMeta(); - - return match(true) { - $meta->isAttribute()->unwrapOr(false) => $onAttribute(), - $property->getName() === '_' => $onValue(), - default => $onElements() - }; + private function runLens(Lens $lens, TypeMeta $meta, mixed $data, mixed $default): mixed + { + try { + /** @var mixed */ + return $this->decorateLensForType($lens, $meta)->get($data); + } catch (Exception $e) { + return $default; + } } /** @@ -214,9 +197,8 @@ private function handleProperty( * * @return Lens */ - private function decorateLensForType(Lens $lens, XsdType $type): Lens + private function decorateLensForType(Lens $lens, TypeMeta $meta): Lens { - $meta = $type->getMeta(); if ($meta->isNullable()->unwrapOr(false)) { return optional($lens); } @@ -237,14 +219,13 @@ private function decorateLensForType(Lens $lens, XsdType $type): Lens private function detectProperties(Context $context): array { $type = (new ComplexTypeBuilder())($context); - $properties = reindex( + + return reindex( sort_by( $type->getProperties(), static fn (Property $property): bool => !$property->getType()->getMeta()->isAttribute()->unwrapOr(false), ), static fn (Property $property): string => $property->getName(), ); - - return $properties; } } From eaa5536cb795c96524380c6ec8af72f88f1ea685 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Wed, 12 Jun 2024 14:19:59 +0200 Subject: [PATCH 3/5] Remove duplicate type check during encoder detection --- src/Encoder/SimpleType/EncoderDetector.php | 5 ----- src/Exception/InvalidArgumentException.php | 8 -------- 2 files changed, 13 deletions(-) delete mode 100644 src/Exception/InvalidArgumentException.php diff --git a/src/Encoder/SimpleType/EncoderDetector.php b/src/Encoder/SimpleType/EncoderDetector.php index 41a8e60..d4463f0 100644 --- a/src/Encoder/SimpleType/EncoderDetector.php +++ b/src/Encoder/SimpleType/EncoderDetector.php @@ -8,7 +8,6 @@ use Soap\Encoding\Encoder\Feature; use Soap\Encoding\Encoder\OptionalElementEncoder; use Soap\Encoding\Encoder\XmlEncoder; -use Soap\Encoding\Exception\InvalidArgumentException; use Soap\Engine\Metadata\Model\XsdType; use function Psl\Iter\any; @@ -22,10 +21,6 @@ public function __invoke(Context $context): XmlEncoder $type = $context->type; $meta = $type->getMeta(); - if (!$meta->isSimple()->unwrapOr(false)) { - throw new InvalidArgumentException('Unable to detect a complex type in the simple type detector'); - } - $encoder = $this->detectSimpleTypeEncoder($type, $context); if (!$encoder instanceof Feature\ListAware && $this->detectIsListType($type)) { $encoder = new SimpleListEncoder($encoder); diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php deleted file mode 100644 index d511176..0000000 --- a/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,8 +0,0 @@ - Date: Wed, 12 Jun 2024 14:54:09 +0200 Subject: [PATCH 4/5] Remove duplicate iso + optimize callstack --- src/Encoder/OptionalElementEncoder.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Encoder/OptionalElementEncoder.php b/src/Encoder/OptionalElementEncoder.php index c78ab2e..0df0c69 100644 --- a/src/Encoder/OptionalElementEncoder.php +++ b/src/Encoder/OptionalElementEncoder.php @@ -32,20 +32,16 @@ public function iso(Context $context): Iso $meta = $type->getMeta(); $elementIso = $this->elementEncoder->iso($context); - $isNullable = $meta->isNullable()->unwrapOr(false); - if (!$isNullable) { + if (!$meta->isNullable()->unwrapOr(false)) { return $elementIso; } - $isNillable = $meta->isNil()->unwrapOr(false); - $elementIso = $this->elementEncoder->iso($context); - return new Iso( /** * @param T|null $raw */ static fn (mixed $raw): string => match (true) { - $raw === null && $isNillable => (new XsdTypeXmlElementWriter())($context, new NilAttributeBuilder()), + $raw === null && $meta->isNil()->unwrapOr(false) => (new XsdTypeXmlElementWriter())($context, new NilAttributeBuilder()), $raw === null => '', default => $elementIso->to($raw), }, From e87d79007c9be80276f6807947fee690c65b7c4d Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Wed, 12 Jun 2024 14:57:33 +0200 Subject: [PATCH 5/5] Move path detection to static function (less closures) --- src/Encoder/ErrorHandlingEncoder.php | 40 +++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Encoder/ErrorHandlingEncoder.php b/src/Encoder/ErrorHandlingEncoder.php index 2dca5df..95cfa41 100644 --- a/src/Encoder/ErrorHandlingEncoder.php +++ b/src/Encoder/ErrorHandlingEncoder.php @@ -30,45 +30,47 @@ public function __construct( public function iso(Context $context): Iso { $innerIso = $this->encoder->iso($context); - $buildPath = static function () use ($context): ?string { - $meta = $context->type->getMeta(); - $isElement = $meta->isElement()->unwrapOr(false); - $isAttribute = $meta->isAttribute()->unwrapOr(false); - if (!$isElement && !$isAttribute) { - return null; - } - - $path = $context->type->getXmlTargetNodeName(); - if ($isAttribute) { - return '@' . $path; - } - - return $path; - }; return new Iso( /** * @psalm-param TData $value * @psalm-return TXml */ - static function (mixed $value) use ($innerIso, $context, $buildPath): mixed { + static function (mixed $value) use ($innerIso, $context): mixed { try { return $innerIso->to($value); } catch (Throwable $exception) { - throw EncodingException::encodingValue($value, $context->type, $exception, $buildPath()); + throw EncodingException::encodingValue($value, $context->type, $exception, self::buildPath($context)); } }, /** * @psalm-param TXml $value * @psalm-return TData */ - static function (mixed $value) use ($innerIso, $context, $buildPath): mixed { + static function (mixed $value) use ($innerIso, $context): mixed { try { return $innerIso->from($value); } catch (Throwable $exception) { - throw EncodingException::decodingValue($value, $context->type, $exception, $buildPath()); + throw EncodingException::decodingValue($value, $context->type, $exception, self::buildPath($context)); } } ); } + + private static function buildPath(Context $context): ?string + { + $meta = $context->type->getMeta(); + $isElement = $meta->isElement()->unwrapOr(false); + $isAttribute = $meta->isAttribute()->unwrapOr(false); + if (!$isElement && !$isAttribute) { + return null; + } + + $path = $context->type->getXmlTargetNodeName(); + if ($isAttribute) { + return '@' . $path; + } + + return $path; + } }