Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 [#711] Don't reuse cache for differently configured class finder instances #712

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 2 deletions src/Discovery/Cache/HardClassFinderComputedCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
use ReflectionClass;
use TheCodingMachine\GraphQLite\Discovery\ClassFinder;

use function sprintf;

class HardClassFinderComputedCache implements ClassFinderComputedCache
{
public function __construct(
private readonly CacheInterface $cache,
)
{
) {
}

/**
Expand All @@ -32,6 +33,7 @@ public function compute(
callable $reduce,
): mixed
{
$key = sprintf('%s.%s', $key, $classFinder->hash());
$result = $this->cache->get($key);

if ($result !== null) {
Expand Down
3 changes: 3 additions & 0 deletions src/Discovery/Cache/SnapshotClassFinderComputedCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use TheCodingMachine\GraphQLite\Cache\FilesSnapshot;
use TheCodingMachine\GraphQLite\Discovery\ClassFinder;

use function sprintf;

/**
* Provides cache for a {@see ClassFinder} based on a {@see filemtime()}.
*
Expand Down Expand Up @@ -49,6 +51,7 @@ public function compute(
callable $reduce,
): mixed
{
$key = sprintf('%s.%s', $key, $classFinder->hash());
$entries = $this->entries($classFinder, $key . '.entries', $map);

return $reduce($entries);
Expand Down
5 changes: 5 additions & 0 deletions src/Discovery/ClassFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
interface ClassFinder extends IteratorAggregate
{
public function withPathFilter(callable $filter): self;

/**
* Path filter does not affect the hash.
*/
public function hash(): string;
}
9 changes: 7 additions & 2 deletions src/Discovery/KcsClassFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class KcsClassFinder implements ClassFinder
{
public function __construct(
private FinderInterface $finder,
)
{
private readonly string $hash,
) {
}

public function withPathFilter(callable $filter): ClassFinder
Expand All @@ -29,4 +29,9 @@ public function getIterator(): Traversable
{
return $this->finder->getIterator();
}

public function hash(): string
{
return $this->hash;
}
}
10 changes: 10 additions & 0 deletions src/Discovery/StaticClassFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
use ReflectionClass;
use Traversable;

use function md5;
use function serialize;

class StaticClassFinder implements ClassFinder
{
/** @var (callable(string): bool)|null */
private mixed $pathFilter = null;

private string|null $hash = null;

/** @param list<class-string> $classes */
public function __construct(
private readonly array $classes,
Expand Down Expand Up @@ -41,4 +46,9 @@ public function getIterator(): Traversable
yield $class => $classReflection;
}
}

public function hash(): string
{
return $this->hash ??= md5(serialize($this->classes));
}
}
5 changes: 4 additions & 1 deletion src/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

use function array_reverse;
use function class_exists;
use function implode;
use function md5;
use function substr;
use function trigger_error;
Expand Down Expand Up @@ -565,6 +566,8 @@ private function createClassFinder(): ClassFinder
$finder = $finder->inNamespace($namespace);
}

return new KcsClassFinder($finder);
$hash = md5(implode(',', $this->namespaces));

return new KcsClassFinder($finder, $hash);
}
}
9 changes: 7 additions & 2 deletions tests/AbstractQueryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache;
use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache;
use TheCodingMachine\GraphQLite\Discovery\ClassFinder;
use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder;
use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder;
use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder;
use TheCodingMachine\GraphQLite\Fixtures\Mocks\MockResolvableInputObjectType;
use TheCodingMachine\GraphQLite\Fixtures\TestObject;
use TheCodingMachine\GraphQLite\Fixtures\TestObject2;
Expand Down Expand Up @@ -67,6 +67,9 @@
use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface;
use TheCodingMachine\GraphQLite\Types\TypeResolver;

use function implode;
use function md5;

abstract class AbstractQueryProvider extends TestCase
{
private $testObjectType;
Expand Down Expand Up @@ -481,7 +484,9 @@ protected function getClassFinder(array|string $namespaces): ClassFinder

$finder = $finder->withFileFinder(new CachedFileFinder(new DefaultFileFinder(), $arrayAdapter));

return new KcsClassFinder($finder);
$hash = md5(implode(',', (array) $namespaces));

return new KcsClassFinder($finder, $hash);
}

protected function getClassFinderComputedCache(): ClassFinderComputedCache
Expand Down
59 changes: 51 additions & 8 deletions tests/Discovery/Cache/HardClassFinderComputedCacheTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Discovery\Cache;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Psr16Cache;
use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder;
Expand All @@ -12,10 +15,12 @@
use TheCodingMachine\GraphQLite\Fixtures\Types\FooType;
use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger;

use function array_values;

#[CoversClass(HardClassFinderComputedCache::class)]
class HardClassFinderComputedCacheTest extends TestCase
{
public function testCachesResultOfReduceFunction(): void
public function testNotReusedCacheBecauseDifferentList(): void
{
$arrayAdapter = new ArrayAdapter();
$arrayAdapter->setLogger(new ExceptionLogger());
Expand All @@ -30,8 +35,46 @@ public function testCachesResultOfReduceFunction(): void
TestType::class,
]),
'key',
fn (\ReflectionClass $reflection) => $reflection->getShortName(),
fn (array $entries) => [array_values($entries)],
static fn (ReflectionClass $reflection) => $reflection->getShortName(),
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame([
'FooType',
'FooExtendType',
'TestType',
], $result);

// Class finder have different class list - result should not be reused from the cache.
// This is necessary to avoid caching issues when there're multiple class finders shares the same cache pool.
[$result] = $classFinderComputedCache->compute(
new StaticClassFinder([FooType::class]),
'key',
static fn (ReflectionClass $reflection) => $reflection->getShortName(),
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame(['FooType'], $result);
}

public function testReusedCacheBecauseSameList(): void
{
$arrayAdapter = new ArrayAdapter();
$arrayAdapter->setLogger(new ExceptionLogger());
$cache = new Psr16Cache($arrayAdapter);

$classFinderComputedCache = new HardClassFinderComputedCache($cache);

$classList = [
FooType::class,
FooExtendType::class,
TestType::class,
];
[$result] = $classFinderComputedCache->compute(
new StaticClassFinder($classList),
'key',
static fn (ReflectionClass $reflection) => $reflection->getShortName(),
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame([
Expand All @@ -40,13 +83,13 @@ public function testCachesResultOfReduceFunction(): void
'TestType',
], $result);

// Even though the class finder and both functions have changed - the result should still be cached.
// Class finder have the same class list - even both functions have changed - the result should be cached.
// This is useful in production, where code and file structure doesn't change.
[$result] = $classFinderComputedCache->compute(
new StaticClassFinder([]),
new StaticClassFinder($classList),
'key',
fn (\ReflectionClass $reflection) => self::fail('Should not be called.'),
fn (array $entries) => self::fail('Should not be called.'),
static fn (ReflectionClass $reflection) => self::fail('Should not be called.'),
static fn (array $entries) => self::fail('Should not be called.'),
);

$this->assertSame([
Expand All @@ -55,4 +98,4 @@ public function testCachesResultOfReduceFunction(): void
'TestType',
], $result);
}
}
}
51 changes: 24 additions & 27 deletions tests/Discovery/Cache/SnapshotClassFinderComputedCacheTest.php
Original file line number Diff line number Diff line change
@@ -1,41 +1,46 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Discovery\Cache;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Psr16Cache;
use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder;
use TheCodingMachine\GraphQLite\Fixtures\TestType;
use TheCodingMachine\GraphQLite\Fixtures\Types\EnumType;
use TheCodingMachine\GraphQLite\Fixtures\Types\FooExtendType;
use TheCodingMachine\GraphQLite\Fixtures\Types\FooType;
use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger;

use function Safe\touch;
use function array_values;
use function clearstatcache;
use function Safe\filemtime;
use function Safe\touch;

#[CoversClass(SnapshotClassFinderComputedCache::class)]
class SnapshotClassFinderComputedCacheTest extends TestCase
{
public function testCachesIndividualEntries(): void
public function testCachesIndividualEntriesSameList(): void
{
$arrayAdapter = new ArrayAdapter();
$arrayAdapter->setLogger(new ExceptionLogger());
$cache = new Psr16Cache($arrayAdapter);

$classFinderComputedCache = new SnapshotClassFinderComputedCache($cache);

$classList = [
FooType::class,
FooExtendType::class,
TestType::class,
];
[$result] = $classFinderComputedCache->compute(
new StaticClassFinder([
FooType::class,
FooExtendType::class,
TestType::class,
]),
new StaticClassFinder($classList),
'key',
fn (\ReflectionClass $reflection) => $reflection->getShortName(),
fn (array $entries) => [array_values($entries)],
static fn (ReflectionClass $reflection) => $reflection->getShortName(),
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame([
Expand All @@ -45,14 +50,10 @@ public function testCachesIndividualEntries(): void
], $result);

[$result] = $classFinderComputedCache->compute(
new StaticClassFinder([
FooType::class,
FooExtendType::class,
TestType::class,
]),
new StaticClassFinder($classList),
'key',
fn (\ReflectionClass $reflection) => self::fail('Should not be called.'),
fn (array $entries) => [array_values($entries)],
static fn (ReflectionClass $reflection) => self::fail('Should not be called.'),
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame([
Expand All @@ -61,23 +62,19 @@ public function testCachesIndividualEntries(): void
'TestType',
], $result);

$this->touch((new \ReflectionClass(FooType::class))->getFileName());
$this->touch((new ReflectionClass(FooType::class))->getFileName());

[$result] = $classFinderComputedCache->compute(
new StaticClassFinder([
FooType::class,
TestType::class,
EnumType::class,
]),
new StaticClassFinder($classList),
'key',
fn (\ReflectionClass $reflection) => $reflection->getShortName() . ' Modified',
fn (array $entries) => [array_values($entries)],
static fn (ReflectionClass $reflection) => $reflection->getShortName() . ' Modified',
static fn (array $entries) => [array_values($entries)],
);

$this->assertSame([
'FooType Modified',
'FooExtendType',
'TestType',
'EnumType Modified',
], $result);
}

Expand All @@ -86,4 +83,4 @@ private function touch(string $fileName): void
touch($fileName, filemtime($fileName) + 1);
clearstatcache();
}
}
}
7 changes: 4 additions & 3 deletions tests/Discovery/KcsClassFinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ class KcsClassFinderTest extends TestCase
{
public function testYieldsGivenClasses(): void
{
$namespaces = 'TheCodingMachine\GraphQLite\Fixtures\Types';
$finder = new KcsClassFinder(
(new ComposerFinder())
->inNamespace('TheCodingMachine\GraphQLite\Fixtures\Types')
(new ComposerFinder())->inNamespace($namespaces),
md5($namespaces)
);

$finderWithPath = $finder->withPathFilter(fn (string $path) => str_contains($path, 'FooExtendType.php'));
Expand Down Expand Up @@ -50,4 +51,4 @@ private function assertFoundClasses(array $expectedClasses, ClassFinder $classFi
$this->assertContainsOnlyInstancesOf(\ReflectionClass::class, $result);
$this->assertEqualsCanonicalizing($expectedClasses, array_keys($result));
}
}
}
Loading
Loading