Skip to content

Commit 4bd7ed1

Browse files
committed
Add cached engine and cached driver implementations
1 parent b0d7d67 commit 4bd7ed1

File tree

8 files changed

+351
-8
lines changed

8 files changed

+351
-8
lines changed

composer.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
"Soap\\CachedEngine\\": "src/"
99
}
1010
},
11+
"autoload-dev": {
12+
"psr-4": {
13+
"SoapTest\\CachedEngine\\": "tests/"
14+
}
15+
},
1116
"authors": [
1217
{
1318
"name": "Toon Verwerft",
@@ -16,15 +21,13 @@
1621
],
1722
"require": {
1823
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
19-
"php-soap/engine": "2.9.0"
20-
},
21-
"autoload-dev": {
22-
"psr-4": {
23-
"SoapTest\\CachedEngine\\": "tests/"
24-
}
24+
"php-soap/engine": "2.9.0",
25+
"psr/cache": "^3.0",
26+
"psr/cache-implementation": "^3.0"
2527
},
2628
"require-dev": {
27-
"phpunit/phpunit": "^9.5",
28-
"vimeo/psalm": "^5.9"
29+
"phpunit/phpunit": "^10.0",
30+
"vimeo/psalm": "^5.9",
31+
"symfony/cache": "^7.0 || ^6.4"
2932
}
3033
}

psalm.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@
1919
<directory name="tests"/>
2020
</ignoreFiles>
2121
</projectFiles>
22+
<ignoreExceptions>
23+
<class name="Psl\Type\Exception\AssertException" />
24+
</ignoreExceptions>
2225
</psalm>

src/CacheConfig.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\CachedEngine;
5+
6+
final class CacheConfig
7+
{
8+
public function __construct(
9+
public readonly string $cacheKey,
10+
public readonly ?int $ttlInSeconds = null,
11+
) {
12+
}
13+
}

src/CachedDriver.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\CachedEngine;
5+
6+
use Closure;
7+
use Psr\Cache\CacheItemPoolInterface;
8+
use Psr\Cache\InvalidArgumentException;
9+
use Soap\Engine\Driver;
10+
use Soap\Engine\HttpBinding\SoapRequest;
11+
use Soap\Engine\HttpBinding\SoapResponse;
12+
use Soap\Engine\Metadata\Metadata;
13+
use function Psl\Type\instance_of;
14+
15+
final class CachedDriver implements Driver
16+
{
17+
private readonly CacheItemPoolInterface $cachePool;
18+
private readonly CacheConfig $cacheConfig;
19+
/**
20+
* @var Closure():Driver
21+
*/
22+
private readonly Closure $driverFactory;
23+
private ?Driver $driver = null;
24+
25+
/**
26+
* @param callable(): Driver $driverFactory
27+
*/
28+
public function __construct(
29+
CacheItemPoolInterface $cachePool,
30+
CacheConfig $cacheConfig,
31+
callable $driverFactory
32+
) {
33+
$this->cachePool = $cachePool;
34+
$this->cacheConfig = $cacheConfig;
35+
$this->driverFactory = $driverFactory(...);
36+
}
37+
38+
/**
39+
* @throws InvalidArgumentException
40+
*/
41+
public function decode(string $method, SoapResponse $response): mixed
42+
{
43+
return $this->grabDriver()->decode($method, $response);
44+
}
45+
46+
/**
47+
* @throws InvalidArgumentException
48+
*/
49+
public function encode(string $method, array $arguments): SoapRequest
50+
{
51+
return $this->grabDriver()->encode($method, $arguments);
52+
}
53+
54+
/**
55+
* @throws InvalidArgumentException
56+
*/
57+
public function getMetadata(): Metadata
58+
{
59+
return $this->grabDriver()->getMetadata();
60+
}
61+
62+
/**
63+
* @throws InvalidArgumentException
64+
*/
65+
private function grabDriver(): Driver
66+
{
67+
if ($this->driver !== null) {
68+
return $this->driver;
69+
}
70+
71+
$item = $this->cachePool->getItem($this->cacheConfig->cacheKey);
72+
if (!$item->isHit()) {
73+
$item->set(($this->driverFactory)());
74+
$item->expiresAfter($this->cacheConfig->ttlInSeconds);
75+
$this->cachePool->save($item);
76+
}
77+
78+
$this->driver = instance_of(Driver::class)->assert($item->get());
79+
80+
return $this->driver;
81+
}
82+
}

src/CachedEngine.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\CachedEngine;
5+
6+
use Closure;
7+
use Psr\Cache\CacheItemPoolInterface;
8+
use Psr\Cache\InvalidArgumentException;
9+
use Soap\Engine\Engine;
10+
use Soap\Engine\Metadata\Metadata;
11+
use function Psl\Type\instance_of;
12+
13+
final class CachedEngine implements Engine
14+
{
15+
private readonly CacheItemPoolInterface $cachePool;
16+
private readonly CacheConfig $cacheConfig;
17+
private readonly Closure $engineFactory;
18+
private ?Engine $engine = null;
19+
20+
public function __construct(
21+
CacheItemPoolInterface $cachePool,
22+
CacheConfig $cacheConfig,
23+
callable $engineFactory
24+
) {
25+
$this->cachePool = $cachePool;
26+
$this->cacheConfig = $cacheConfig;
27+
$this->engineFactory = $engineFactory(...);
28+
}
29+
30+
/**
31+
* @psalm-return mixed
32+
*
33+
* @throws InvalidArgumentException
34+
*/
35+
public function request(string $method, array $arguments): mixed
36+
{
37+
return $this->grabEngine()->request($method, $arguments);
38+
}
39+
40+
41+
/**
42+
* @throws InvalidArgumentException
43+
*/
44+
public function getMetadata(): Metadata
45+
{
46+
return $this->grabEngine()->getMetadata();
47+
}
48+
49+
/**
50+
* @throws InvalidArgumentException
51+
*/
52+
private function grabEngine(): Engine
53+
{
54+
if ($this->engine !== null) {
55+
return $this->engine;
56+
}
57+
58+
$item = $this->cachePool->getItem($this->cacheConfig->cacheKey);
59+
if (!$item->isHit()) {
60+
$item->set(($this->engineFactory)());
61+
$item->expiresAfter($this->cacheConfig->ttlInSeconds);
62+
63+
$this->cachePool->save($item);
64+
}
65+
66+
$this->engine = instance_of(Engine::class)->assert($item->get());
67+
68+
return $this->engine;
69+
}
70+
}

tests/Unit/CacheTrait.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SoapTest\CachedEngine\Unit;
5+
6+
use Psr\Cache\CacheItemPoolInterface;
7+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
8+
9+
trait CacheTrait
10+
{
11+
private static function createCachePool(): CacheItemPoolInterface
12+
{
13+
return new ArrayAdapter();
14+
}
15+
}

tests/Unit/CachedDriverTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SoapTest\CachedEngine\Unit;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Soap\CachedEngine\CacheConfig;
9+
use Soap\CachedEngine\CachedDriver;
10+
use Soap\Engine\Exception\DriverException;
11+
use Soap\Engine\HttpBinding\SoapResponse;
12+
use Soap\Engine\Metadata\Collection\MethodCollection;
13+
use Soap\Engine\Metadata\Collection\TypeCollection;
14+
use Soap\Engine\Metadata\InMemoryMetadata;
15+
use Soap\Engine\PartialDriver;
16+
17+
#[CoversClass(CachedDriver::class)]
18+
#[CoversClass(CacheConfig::class)]
19+
final class CachedDriverTest extends TestCase
20+
{
21+
use CacheTrait;
22+
23+
public function test_it_lazily_loads_driver_from_cache_on_metadata_access(): void
24+
{
25+
$cachePool = self::createCachePool();
26+
$cacheConfig = new CacheConfig('key', 99);
27+
$cachedDriver = new CachedDriver($cachePool, $cacheConfig, self::createDriverImplementation(...));
28+
$internalDriver = self::createDriverImplementation();
29+
30+
static::assertFalse($cachePool->getItem('key')->isHit());
31+
static::assertEquals($cachedDriver->getMetadata(), $internalDriver->getMetadata());
32+
static::assertTrue($cachePool->getItem('key')->isHit());
33+
34+
// It keeps driver in memory
35+
$cachePool->deleteItem('key');
36+
static::assertEquals($cachedDriver->getMetadata(), $internalDriver->getMetadata());
37+
static::assertFalse($cachePool->getItem('key')->isHit());
38+
}
39+
40+
public function test_it_lazily_loads_driver_from_cache_on_encode_request(): void
41+
{
42+
$cachePool = self::createCachePool();
43+
$cacheConfig = new CacheConfig('key', 99);
44+
$cachedDriver = new CachedDriver($cachePool, $cacheConfig, self::createDriverImplementation(...));
45+
$internalDriver = self::createDriverImplementation();
46+
47+
static::assertFalse($cachePool->getItem('key')->isHit());
48+
try {
49+
$cachedDriver->encode('method', []);
50+
} catch (DriverException) {
51+
// Encode is not implemented ;)
52+
}
53+
static::assertTrue($cachePool->getItem('key')->isHit());
54+
55+
// It keeps driver in memory
56+
$cachePool->deleteItem('key');
57+
static::assertEquals($cachedDriver->getMetadata(), $internalDriver->getMetadata());
58+
static::assertFalse($cachePool->getItem('key')->isHit());
59+
}
60+
61+
public function test_it_lazily_loads_driver_from_cache_on_decode_request(): void
62+
{
63+
$cachePool = self::createCachePool();
64+
$cacheConfig = new CacheConfig('key', 99);
65+
$cachedDriver = new CachedDriver($cachePool, $cacheConfig, self::createDriverImplementation(...));
66+
$internalDriver = self::createDriverImplementation();
67+
68+
static::assertFalse($cachePool->getItem('key')->isHit());
69+
try {
70+
$cachedDriver->decode('method', new SoapResponse('response'));
71+
} catch (DriverException) {
72+
// Encode is not implemented ;)
73+
}
74+
static::assertTrue($cachePool->getItem('key')->isHit());
75+
76+
// It keeps driver in memory
77+
$cachePool->deleteItem('key');
78+
static::assertEquals($cachedDriver->getMetadata(), $internalDriver->getMetadata());
79+
static::assertFalse($cachePool->getItem('key')->isHit());
80+
}
81+
82+
83+
public static function createDriverImplementation(): PartialDriver
84+
{
85+
return new PartialDriver(
86+
metadata: new InMemoryMetadata(
87+
new TypeCollection(),
88+
new MethodCollection()
89+
)
90+
);
91+
}
92+
}

tests/Unit/CachedEngineTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SoapTest\CachedEngine\Unit;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Soap\CachedEngine\CacheConfig;
9+
use Soap\CachedEngine\CachedEngine;
10+
use Soap\Engine\Exception\DriverException;
11+
use Soap\Engine\NoopTransport;
12+
use Soap\Engine\SimpleEngine;
13+
14+
#[CoversClass(CachedEngine::class)]
15+
#[CoversClass(CacheConfig::class)]
16+
final class CachedEngineTest extends TestCase
17+
{
18+
use CacheTrait;
19+
20+
public function test_it_lazily_loads_driver_from_cache_on_metadata_access(): void
21+
{
22+
$cachePool = self::createCachePool();
23+
$cacheConfig = new CacheConfig('key', 99);
24+
$cachedEngine = new CachedEngine($cachePool, $cacheConfig, self::createEngineImplementation(...));
25+
$internalEngine = self::createEngineImplementation();
26+
27+
static::assertFalse($cachePool->getItem('key')->isHit());
28+
static::assertEquals($cachedEngine->getMetadata(), $internalEngine->getMetadata());
29+
static::assertTrue($cachePool->getItem('key')->isHit());
30+
31+
// It keeps driver in memory
32+
$cachePool->deleteItem('key');
33+
static::assertEquals($cachedEngine->getMetadata(), $cachedEngine->getMetadata());
34+
static::assertFalse($cachePool->getItem('key')->isHit());
35+
}
36+
37+
public function test_it_lazily_loads_driver_from_cache_on_encode_request(): void
38+
{
39+
$cachePool = self::createCachePool();
40+
$cacheConfig = new CacheConfig('key', 99);
41+
$cachedEngine = new CachedEngine($cachePool, $cacheConfig, self::createEngineImplementation(...));
42+
$internalEngine = self::createEngineImplementation();
43+
44+
static::assertFalse($cachePool->getItem('key')->isHit());
45+
try {
46+
$cachedEngine->request('method', []);
47+
} catch (DriverException) {
48+
// Encode is not implemented ;)
49+
}
50+
static::assertTrue($cachePool->getItem('key')->isHit());
51+
52+
// It keeps driver in memory
53+
$cachePool->deleteItem('key');
54+
static::assertEquals($cachedEngine->getMetadata(), $internalEngine->getMetadata());
55+
static::assertFalse($cachePool->getItem('key')->isHit());
56+
}
57+
58+
public static function createEngineImplementation(): SimpleEngine
59+
{
60+
return new SimpleEngine(
61+
CachedDriverTest::createDriverImplementation(),
62+
new NoopTransport()
63+
);
64+
}
65+
}

0 commit comments

Comments
 (0)