Skip to content

Commit 15d1860

Browse files
authored
Merge pull request #57 from veewee/feature/object-access-cache
Cache ObjectAccess::forContext() using ScopedCache
2 parents 8dbfbbc + 6133810 commit 15d1860

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/Encoder/ObjectAccess.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace Soap\Encoding\Encoder;
55

6+
use Soap\Encoding\Cache\ScopedCache;
7+
use Soap\Encoding\EncoderRegistry;
68
use Soap\Encoding\Normalizer\PhpPropertyNameNormalizer;
79
use Soap\Encoding\TypeInference\ComplexTypeBuilder;
810
use Soap\Engine\Metadata\Model\Property;
@@ -32,7 +34,34 @@ public function __construct(
3234
) {
3335
}
3436

37+
/**
38+
* @return ScopedCache<EncoderRegistry, self>
39+
*
40+
* @psalm-suppress LessSpecificReturnStatement, MoreSpecificReturnType, MixedReturnStatement
41+
*/
42+
private static function cache(): ScopedCache
43+
{
44+
static $cache = new ScopedCache();
45+
46+
return $cache;
47+
}
48+
49+
private static function cacheKey(Context $context): string
50+
{
51+
return $context->type->getXmlNamespace() . '|' . $context->type->getName()
52+
. '|' . $context->bindingUse->value;
53+
}
54+
3555
public static function forContext(Context $context): self
56+
{
57+
return self::cache()->lookup(
58+
$context->registry,
59+
self::cacheKey($context),
60+
static fn (): self => self::build($context)
61+
);
62+
}
63+
64+
private static function build(Context $context): self
3665
{
3766
$type = ComplexTypeBuilder::default()($context);
3867

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\Unit\Encoder;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Soap\Encoding\Encoder\Context;
9+
use Soap\Encoding\Encoder\ObjectAccess;
10+
use Soap\Encoding\EncoderRegistry;
11+
use Soap\Encoding\Test\Unit\ContextCreatorTrait;
12+
use Soap\WsdlReader\Model\Definitions\BindingUse;
13+
14+
/**
15+
* Verifies that ObjectAccess::forContext() cache returns correct results
16+
* when the same type is used with different contexts.
17+
*/
18+
#[CoversClass(ObjectAccess::class)]
19+
final class ObjectAccessCacheTest extends TestCase
20+
{
21+
use ContextCreatorTrait;
22+
23+
private const SCHEMA = <<<'EOXML'
24+
<complexType name="testType">
25+
<sequence>
26+
<element name="name" type="xsd:string"/>
27+
<element name="value" type="xsd:int"/>
28+
</sequence>
29+
</complexType>
30+
EOXML;
31+
32+
/**
33+
* Same type with literal vs encoded binding must produce different ObjectAccess
34+
* instances (the isos capture bindingUse-dependent behavior).
35+
*/
36+
public function test_different_binding_use_produces_different_object_access(): void
37+
{
38+
$metadata = self::createMetadataFromWsdl(self::SCHEMA, 'type="tns:testType"');
39+
$type = $metadata->getTypes()->fetchFirstByName('testType');
40+
$registry = EncoderRegistry::default();
41+
$namespaces = self::buildNamespaces();
42+
43+
$literalContext = new Context($type->getXsdType(), $metadata, $registry, $namespaces, BindingUse::LITERAL);
44+
$encodedContext = new Context($type->getXsdType(), $metadata, $registry, $namespaces, BindingUse::ENCODED);
45+
46+
$literalAccess = ObjectAccess::forContext($literalContext);
47+
$encodedAccess = ObjectAccess::forContext($encodedContext);
48+
49+
// Both should have the same properties
50+
static::assertSame(
51+
array_keys($literalAccess->properties),
52+
array_keys($encodedAccess->properties)
53+
);
54+
55+
// But they must be different instances (different isos due to bindingUse)
56+
static::assertNotSame($literalAccess, $encodedAccess);
57+
58+
// Encoded adds xsi:type attribute, literal does not
59+
$literalXml = $literalAccess->isos['name']->to('hello');
60+
$encodedXml = $encodedAccess->isos['name']->to('hello');
61+
62+
static::assertStringNotContainsString('xsi:type', $literalXml);
63+
static::assertStringContainsString('xsi:type', $encodedXml);
64+
}
65+
66+
/**
67+
* Same type on different registries must not share cache entries.
68+
*/
69+
public function test_different_registries_produce_separate_cache_entries(): void
70+
{
71+
$metadata = self::createMetadataFromWsdl(self::SCHEMA, 'type="tns:testType"');
72+
$type = $metadata->getTypes()->fetchFirstByName('testType');
73+
$namespaces = self::buildNamespaces();
74+
75+
$context1 = new Context($type->getXsdType(), $metadata, EncoderRegistry::default(), $namespaces);
76+
$context2 = new Context($type->getXsdType(), $metadata, EncoderRegistry::default(), $namespaces);
77+
78+
static::assertNotSame($context1->registry, $context2->registry);
79+
80+
$access1 = ObjectAccess::forContext($context1);
81+
$access2 = ObjectAccess::forContext($context2);
82+
83+
// Both valid, same structure, but different instances (different registries)
84+
static::assertNotSame($access1, $access2);
85+
static::assertSame(array_keys($access1->properties), array_keys($access2->properties));
86+
}
87+
88+
/**
89+
* Repeated calls with the same context return the cached instance.
90+
*/
91+
public function test_same_context_returns_cached_instance(): void
92+
{
93+
$metadata = self::createMetadataFromWsdl(self::SCHEMA, 'type="tns:testType"');
94+
$context = self::createContextFromMetadata($metadata, 'testType');
95+
96+
$access1 = ObjectAccess::forContext($context);
97+
$access2 = ObjectAccess::forContext($context);
98+
99+
static::assertSame($access1, $access2);
100+
}
101+
}

0 commit comments

Comments
 (0)