Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5937c5d
Add ElementLookup to read an element without mixing attributes and ch…
veewee Oct 15, 2026
687ba61
Decode object properties through ElementLookup
veewee Oct 15, 2026
4660d61
Read attributes by exact namespace only
veewee Oct 15, 2026
7044b66
Hand custom decoder lenses the ElementLookup
veewee Oct 15, 2026
fda0a6c
Require the wsdl-reader branch that qualifies ref attributes
veewee Oct 15, 2026
bc8ad5e
Write and read ref attributes in their own namespace
veewee Oct 15, 2026
54e3f09
Profile ElementLookup next to the legacy lookup array reader
veewee Oct 15, 2026
7027627
Remove DocumentToLookupArrayReader and ElementLookup::toArray
veewee Oct 15, 2026
ce051f3
Walk element children directly when building the lookup
veewee Oct 15, 2026
039966c
Merge branch 'absent-optional-elements' into element-lookup
veewee Oct 15, 2026
e8e9ca2
Skip the encoder for null optional elements that write nothing
veewee Oct 15, 2026
c976f42
Pin absent, trailing and nested null optional elements byte for byte
veewee Oct 15, 2026
755bfc1
Read the xsi namespace once instead of per decoded element
veewee Oct 15, 2026
353a61c
Read element values from the node directly
veewee Oct 15, 2026
085fe11
Let the encoder say whether null writes nothing
veewee Oct 16, 2026
d8b7f19
Skip null optional elements without writing anything
veewee Oct 16, 2026
4695a97
Merge remote-tracking branch 'upstream/element-lookup' into perf-chea…
veewee Oct 16, 2026
6e7abd1
Let the encoder decide what null does for a property
veewee Oct 16, 2026
41bb8e7
Describe SkipWhenAbsent without xsi:nil
veewee Oct 16, 2026
64a9906
Spell out what each null value handling case writes
veewee Oct 16, 2026
36e63a3
Write onNull as one match
veewee Oct 16, 2026
3b5bc57
Merge absent-optional-elements and require wsdl-reader 0.34
veewee Oct 16, 2026
ccddfcc
Merge pull request #80 from php-soap/element-lookup
veewee Oct 16, 2026
85c702f
Read built in decoder properties from ElementLookup directly
veewee Oct 15, 2026
d74891b
Compare built in decoder lenses with the direct read on absent values…
veewee Oct 15, 2026
0caac81
Read every property through a plain closure instead of runLens
veewee Oct 16, 2026
1794304
Let a throwing custom object lens fail the encoding
veewee Oct 16, 2026
36830fb
Pin the custom decode reader and keep the full read type
veewee Oct 16, 2026
a9cc817
Build the encode and decode readers the same way
veewee Oct 16, 2026
7b37bfa
Merge pull request #83 from php-soap/perf-skip-absent-non-nillable-el…
veewee Oct 16, 2026
27fc514
Merge pull request #85 from veewee/perf-cheaper-decode-reads
veewee Oct 16, 2026
875a36d
Merge absent-optional-elements (#83) into perf-direct-element-lookup
veewee Oct 16, 2026
f8c199a
Merge element-lookup (#85) into perf-direct-element-lookup
veewee Oct 16, 2026
dde12e2
Merge pull request #84 from php-soap/perf-direct-element-lookup
veewee Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions benchmarks/scripts/profile_encode.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@
}
printf("typeIso->from(\$element) (no parse): %6.1fus/call\n", (hrtime(true) - $t0) / 1e3 / $N);

// Step 4: Just DocumentToLookupArrayReader
// Step 4: ElementLookup, what ObjectEncoder decodes from
$t0 = hrtime(true);
for ($i = 0; $i < $N; $i++) {
(new \Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader())($partElement);
\Soap\Encoding\Xml\Node\ElementLookup::fromElement($partElement);
}
printf("DocumentToLookupArrayReader: %6.1fus/call\n", (hrtime(true) - $t0) / 1e3 / $N);
printf("ElementLookup::fromElement: %6.1fus/call\n", (hrtime(true) - $t0) / 1e3 / $N);

// Step 5: object_data from (properties_set)
$objectDataIso = \VeeWee\Reflecta\Iso\object_data(\stdClass::class);
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"php-soap/engine": "^2.21",
"php-soap/wsdl": "^1.19",
"php-soap/xml": "^1.10",
"php-soap/wsdl-reader": "~0.32"
"php-soap/wsdl-reader": "~0.34"
},
"require-dev": {
"vimeo/psalm": "~6.13",
Expand Down
31 changes: 12 additions & 19 deletions src/Encoder/AnyElementEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Soap\Encoding\Xml\Node\Element;
use Soap\Encoding\Xml\Node\ElementList;
use Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader;
use Soap\Encoding\Xml\Node\ElementLookup;
use Soap\Engine\Metadata\Model\Property;
use Soap\Engine\Metadata\Model\Type;
use VeeWee\Reflecta\Iso\Iso;
Expand All @@ -21,9 +21,7 @@
/**
* @implements XmlEncoder<array|string|null, array<array-key, string>|string|null, string, ElementList|string>
*
* @psalm-import-type LookupArray from DocumentToLookupArrayReader
*
* @template-implements Feature\ProvidesObjectDecoderLens<LookupArray, LookupArray, ElementList, ElementList>
* @template-implements Feature\ProvidesObjectDecoderLens<ElementLookup, ElementLookup, ElementList, ElementList>
*/
final class AnyElementEncoder implements Feature\ListAware, Feature\OptionalAware, Feature\ProvidesObjectDecoderLens, XmlEncoder
{
Expand All @@ -32,32 +30,27 @@ final class AnyElementEncoder implements Feature\ListAware, Feature\OptionalAwar
* It will contain all the XML tags available in the object that is surrounding the 'any' property.
* Properties that are already known by the object, will be omitted.
*
* @return Lens<LookupArray, LookupArray, ElementList, ElementList>
* @return Lens<ElementLookup, ElementLookup, ElementList, ElementList>
*/
public static function createObjectDecoderLens(Type $parentType, Property $currentProperty): Lens
{
$omittedKeys = reduce(
$omittedKeys = array_flip(reduce(
$parentType->getProperties(),
static fn (array $omit, Property $property): array => [
...$omit,
...($property->getName() !== $currentProperty->getName() ? [$property->getName()] : []),
],
[]
);
));

/**
* @param LookupArray $data
* @return LookupArray
*/
$omit = static fn (array $data): array => diff_by_key($data, array_flip($omittedKeys));

/** @var Lens<LookupArray, LookupArray, ElementList, ElementList> */
/** @var Lens<ElementLookup, ElementLookup, ElementList, ElementList> */
return Lens::readonly(
/**
* @psalm-suppress MixedArgumentTypeCoercion - Psalm gets confused about the result of omit.
* @param LookupArray $data
*/
static fn (array $data): ElementList => ElementList::fromLookupArray($omit($data))
static function (ElementLookup $lookup) use ($omittedKeys): ElementList {
/** @var array<string, Element|ElementList> $children */
$children = diff_by_key($lookup->children(), $omittedKeys);

return ElementList::fromLookupArray($children);
}
);
}

Expand Down
9 changes: 8 additions & 1 deletion src/Encoder/ErrorHandlingEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @implements XmlEncoder<TDataIn, TDataOut, TXmlOut, TXmlIn>
* @implements Feature\DecoratingEncoder<TDataIn, TDataOut, TXmlOut, TXmlIn>
*/
final class ErrorHandlingEncoder implements Feature\DecoratingEncoder, XmlEncoder
final class ErrorHandlingEncoder implements Feature\DecoratingEncoder, Feature\NullValueAware, XmlEncoder
{
/**
* @param XmlEncoder<TDataIn, TDataOut, TXmlOut, TXmlIn> $encoder
Expand All @@ -34,6 +34,13 @@ public function decoratedEncoder(): XmlEncoder
return $this->encoder;
}

public function nullValueHandling(Context $context): NullValueHandling
{
return $this->encoder instanceof Feature\NullValueAware
? $this->encoder->nullValueHandling($context)
: NullValueHandling::Write;
}

/**
* @return Iso<TDataIn, TDataOut, TXmlOut, TXmlIn>
*/
Expand Down
14 changes: 14 additions & 0 deletions src/Encoder/Feature/NullValueAware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types=1);

namespace Soap\Encoding\Encoder\Feature;

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\NullValueHandling;

/**
* Tells an object encoder what to do with a null value in this context, so it can leave the element out without calling the encoder.
*/
interface NullValueAware
{
public function nullValueHandling(Context $context): NullValueHandling;
}
6 changes: 4 additions & 2 deletions src/Encoder/Feature/ProvidesObjectDecoderLens.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

namespace Soap\Encoding\Encoder\Feature;

use Soap\Encoding\Xml\Node\ElementLookup;
use Soap\Engine\Metadata\Model\Property;
use Soap\Engine\Metadata\Model\Type;
use VeeWee\Reflecta\Lens\LensInterface;

/**
* When an encoder implements this feature interface, it knows how to create a lens that will be applied on the parent data that is being decoded.
* The lens reads from the ElementLookup of the parent element, for example $lookup->element('name') or $lookup->attribute('name').
*
* @template-covariant S of array<array-key, mixed>
* @template-covariant T of array<array-key, mixed>
* @template-covariant S of ElementLookup
* @template-covariant T of ElementLookup
* @template-covariant A
* @template-covariant B
*/
Expand Down
27 changes: 27 additions & 0 deletions src/Encoder/NullValueHandling.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace Soap\Encoding\Encoder;

/**
* What ObjectEncoder does with a property that is null or absent from the PHP value.
* An absent property reads as null, unless the case below tells them apart.
*/
enum NullValueHandling
{
/**
* Pass null to the property's encoder and write what it returns.
* For a nillable element that is the element with xsi:nil="true".
*/
case Write;

/**
* Write nothing for the property, so the element is left out.
*/
case Skip;

/**
* Write nothing when the property is absent from the PHP value.
* When it is set to null, behave like Write.
*/
case SkipWhenAbsent;
}
Loading
Loading