Skip to content

Commit e8c60ea

Browse files
authored
fix(core): properly handle unserializable discovery items when caching discovery (#1503)
1 parent 8da558f commit e8c60ea

File tree

7 files changed

+115
-1
lines changed

7 files changed

+115
-1
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Tempest\Core;
4+
5+
use Exception;
6+
use Tempest\Discovery\DiscoveryLocation;
7+
8+
final class CouldNotStoreDiscoveryCache extends Exception
9+
{
10+
public function __construct(DiscoveryLocation $location)
11+
{
12+
parent::__construct(sprintf(
13+
'Could not store discovery cache for %s. This is likely because you\'re trying to store unserializable data like reflection classes or closures in discovery items.',
14+
$location->path,
15+
));
16+
}
17+
}

packages/core/src/DiscoveryCache.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ public function store(DiscoveryLocation $location, array $discoveries): void
6868
->getItem($location->key)
6969
->set($cachedForLocation);
7070

71-
$this->pool->save($item);
71+
$saved = $this->pool->save($item);
72+
73+
if (! $saved) {
74+
throw new CouldNotStoreDiscoveryCache($location);
75+
}
7276
}
7377

7478
public function clear(): void

packages/discovery/src/DiscoveryItems.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public function getForLocation(DiscoveryLocation $location): array
3131
return $this->items[$location->path] ?? [];
3232
}
3333

34+
/**
35+
* Add items to discover.
36+
* Whatever input you add here should be serializable,
37+
* otherwise that input cannot be cached,
38+
* and you'll run into errors when discovery cache is enabled
39+
*/
3440
public function add(DiscoveryLocation $location, mixed $value): self
3541
{
3642
$this->items[$location->path] ??= [];

packages/reflection/src/ClassReflector.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,14 @@ private function memoize(string $key, Closure $closure): mixed
178178

179179
return $this->memoize[$key];
180180
}
181+
182+
public function __serialize(): array
183+
{
184+
return ['name' => $this->getName()];
185+
}
186+
187+
public function __unserialize(array $data): void
188+
{
189+
$this->reflectionClass = new PHPReflectionClass($data['name']);
190+
}
181191
}

packages/reflection/tests/ClassReflectorTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,14 @@ public function test_recursive_attribute_from_parent(): void
6060
$this->assertNull($reflector->getAttribute(RecursiveAttribute::class));
6161
$this->assertNotNull($reflector->getAttribute(RecursiveAttribute::class, recursive: true));
6262
}
63+
64+
public function test_serialize(): void
65+
{
66+
$reflector = new ClassReflector(TestClassA::class);
67+
68+
$serialized = serialize($reflector);
69+
$unserialized = unserialize($serialized);
70+
71+
$this->assertEquals($reflector, $unserialized);
72+
}
6373
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Core;
4+
5+
use Tempest\Core\CouldNotStoreDiscoveryCache;
6+
use Tempest\Core\DiscoveryCache;
7+
use Tempest\Discovery\DiscoveryLocation;
8+
use Tests\Tempest\Integration\Core\Fixtures\TestDiscovery;
9+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
10+
11+
use function Tempest\reflect;
12+
13+
final class DiscoveryCacheTest extends FrameworkIntegrationTestCase
14+
{
15+
public function test_exception_with_unserializable_discovery_items(): void
16+
{
17+
$this->assertException(CouldNotStoreDiscoveryCache::class, function () {
18+
$discoveryCache = $this->container->get(DiscoveryCache::class);
19+
20+
$location = new DiscoveryLocation('Test\\', '.');
21+
$discovery = new TestDiscovery();
22+
$discovery->discover($location, reflect($this));
23+
24+
$discoveryCache->store($location, [
25+
$discovery,
26+
]);
27+
});
28+
}
29+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Core\Fixtures;
4+
5+
use Exception;
6+
use Serializable;
7+
use Tempest\Discovery\Discovery;
8+
use Tempest\Discovery\DiscoveryItems;
9+
use Tempest\Discovery\DiscoveryLocation;
10+
use Tempest\Discovery\IsDiscovery;
11+
use Tempest\Reflection\ClassReflector;
12+
13+
final class TestDiscovery implements Discovery
14+
{
15+
use IsDiscovery;
16+
17+
public function __construct()
18+
{
19+
$this->discoveryItems = new DiscoveryItems();
20+
}
21+
22+
public function discover(DiscoveryLocation $location, ClassReflector $class): void
23+
{
24+
$class = new class {
25+
public function __serialize(): array
26+
{
27+
throw new Exception('Cannot serialize');
28+
}
29+
};
30+
31+
$this->discoveryItems->add($location, $class);
32+
}
33+
34+
public function apply(): void
35+
{
36+
// Nothing
37+
}
38+
}

0 commit comments

Comments
 (0)