diff --git a/.github/workflows/backward-compatibility-check.yml b/.github/workflows/backward-compatibility-check.yml index e4cccf12..71083207 100644 --- a/.github/workflows/backward-compatibility-check.yml +++ b/.github/workflows/backward-compatibility-check.yml @@ -5,6 +5,10 @@ name: "Backward Compatibility Check" on: pull_request: +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: roave-backwards-compatibility-check: name: "Backward Compatibility Check" @@ -27,7 +31,7 @@ jobs: fetch-depth: 0 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 23ef92b0..e70800d2 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -5,6 +5,10 @@ name: "Benchmark" on: pull_request: +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: phpbench: name: "Benchmark" @@ -22,11 +26,11 @@ jobs: steps: - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: coverage: "none" php-version: "${{ matrix.php-version }}" - ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M + ini-values: memory_limit=-1 - name: "Checkout base" uses: actions/checkout@v7 @@ -38,7 +42,7 @@ jobs: dependency-versions: ${{ matrix.dependencies }} - name: "phpbench on base" - run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=default --tag=base" + run: "vendor/bin/phpbench run tests/Benchmark --progress=none --tag=base" - name: "Checkout" uses: actions/checkout@v7 @@ -50,18 +54,35 @@ jobs: dependency-versions: ${{ matrix.dependencies }} - name: "phpbench diff" + id: bench + continue-on-error: true run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=diff --ref=base > bench.txt" - - name: "Get Bench Result" - id: phpbench - run: | - echo 'BENCH_RESULT<> $GITHUB_ENV - cat bench.txt >> $GITHUB_ENV - echo 'EOF' >> $GITHUB_ENV - - uses: actions/github-script@v9 with: script: | + const fs = require('fs'); + + // the diff report prints one section per benchmark class: a title line, a console table, a blank line. + // The console table becomes a markdown table by dropping the outer borders and turning the border + // below the header into the separator row. + const sections = fs.readFileSync('bench.txt', 'utf8').trim().split(/\n\s*\n/).map((section) => { + const [title, , header, separator, ...rows] = section.trim().split('\n'); + const table = [header, separator.replace(/\+/g, '|'), ...rows.filter((row) => !row.startsWith('+'))]; + + return `### ${title.replace(/Bench$/, '')}\n\n${table.join('\n')}`; + }).join('\n\n'); + + const failed = ${{ steps.bench.outcome == 'failure' }}; + const commentBody = `Hello :wave: + + Here are the most recent benchmark results, compared against \`${{ github.base_ref }}\`. Time is the mode over the iterations in the \`its\` column, with the relative standard deviation in brackets. + + ${sections} + + ${failed ? ':warning: At least one benchmark got slower than the tolerance in phpbench.json allows.' : ''} + This comment gets updated every time a new commit comes in.`; + // Get the existing comments. const {data: comments} = await github.rest.issues.listComments({ owner: context.repo.owner, @@ -69,27 +90,8 @@ jobs: issue_number: context.payload.number, }) - // Find any comment already made by the bot. - const botComment = comments.find(comment => comment.user.id === 41898282) - const commentBody = ` - - Hello :wave: - -
- here is the most recent benchmark result: - -

- - \`\`\` - ${{ env.BENCH_RESULT }} - \`\`\` - -

-
- - This comment gets update everytime a new commit comes in! - - `; + // Find any comment already made by the bot. + const botComment = comments.find(comment => comment.user.id === 41898282) if (!context.payload.pull_request.head.repo.full_name.startsWith('patchlevel/')) { console.log('Not attempting to write comment on PR from fork'); @@ -110,3 +112,7 @@ jobs: }) } } + + - name: "Fail on regression" + if: steps.bench.outcome == 'failure' + run: "exit 1" diff --git a/.github/workflows/coding-standard.yml b/.github/workflows/coding-standard.yml index ebe345c9..76af27a3 100644 --- a/.github/workflows/coding-standard.yml +++ b/.github/workflows/coding-standard.yml @@ -9,6 +9,10 @@ on: - "[0-9]+.[0-9]+.x" - "renovate/*" +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: static-analysis-coding-standard: name: "Static Analysis by patchlevel/coding-standard" @@ -29,7 +33,7 @@ jobs: uses: actions/checkout@v7 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index b6c8c9fb..fc3ad5e2 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -8,6 +8,10 @@ on: branches: - "[0-9]+.[0-9]+.x" +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: checkdocs: name: "Check Docs" @@ -28,7 +32,7 @@ jobs: uses: actions/checkout@v7 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: coverage: none php-version: "${{ matrix.php-version }}" diff --git a/.github/workflows/mutation-tests-diff.yml b/.github/workflows/mutation-tests-diff.yml index 2add7db0..b187988c 100644 --- a/.github/workflows/mutation-tests-diff.yml +++ b/.github/workflows/mutation-tests-diff.yml @@ -5,6 +5,10 @@ name: "Mutation tests on diff" on: pull_request: +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: mutation-tests-diff: name: "Mutation tests on diff" @@ -27,7 +31,7 @@ jobs: fetch-depth: 0 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: coverage: "pcov" php-version: "${{ matrix.php-version }}" diff --git a/.github/workflows/mutation-tests.yml b/.github/workflows/mutation-tests.yml index 22599648..fea09354 100644 --- a/.github/workflows/mutation-tests.yml +++ b/.github/workflows/mutation-tests.yml @@ -9,6 +9,10 @@ on: - "[0-9]+.[0-9]+.x" - "renovate/*" +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: mutation-tests: name: "Mutation tests" @@ -29,7 +33,7 @@ jobs: uses: actions/checkout@v7 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: coverage: "pcov" php-version: "${{ matrix.php-version }}" diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index a658039f..abbf99ed 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -9,6 +9,10 @@ on: - "[0-9]+.[0-9]+.x" - "renovate/*" +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: static-analysis-phpstan: name: "Static Analysis by PHPStan" @@ -29,7 +33,7 @@ jobs: uses: actions/checkout@v7 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 1396ecc3..07ee4bc2 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -9,6 +9,10 @@ on: - "[0-9]+.[0-9]+.x" - "renovate/*" +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + jobs: phpunit: name: "Unit tests" @@ -46,7 +50,7 @@ jobs: uses: actions/checkout@v7 - name: "Install PHP" - uses: "shivammathur/setup-php@2.37.2" + uses: "shivammathur/setup-php@2.40.0" with: coverage: "pcov" php-version: "${{ matrix.php-version }}" diff --git a/Makefile b/Makefile index 303231f9..fae60328 100644 --- a/Makefile +++ b/Makefile @@ -39,13 +39,13 @@ snapshot: vendor UPDATE_SNAPSHOTS=1 vendor/bin/phpunit --no-coverage --filter testGeneratedCodeMatchesSnapshot .PHONY: benchmark -benchmark: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark --report=default +benchmark: vendor ## run all benchmarks + vendor/bin/phpbench run --report=diff -.PHONY: benchmark-diff-test -benchmark-diff-test: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark --revs=1 --report=default --progress=none --tag=base - vendor/bin/phpbench run tests/Benchmark --revs=1 --report=diff --progress=none --ref=base +.PHONY: benchmark-diff +benchmark-diff: vendor ## run the benchmarks twice and compare the second run against the first, like the CI does + vendor/bin/phpbench run --progress=none --tag=base + vendor/bin/phpbench run --progress=none --report=diff --ref=base .PHONY: docs diff --git a/phpbench.json b/phpbench.json index 729b39d4..60c604cd 100644 --- a/phpbench.json +++ b/phpbench.json @@ -1,54 +1,37 @@ { - "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", "runner.file_pattern": "*Bench.php", + "runner.iterations": 5, + "runner.php_config": { + "memory_limit": "-1", + "opcache.enable_cli": 1, + "opcache.jit_buffer_size": 0, + "xdebug.mode": "off" + }, + "runner.assert": "mode(variant.time.avg) <= mode(baseline.time.avg) +/- 25%", "report.generators": { "diff": { - "generator": "component", - "partition": ["benchmark_name"], - "components": [ - { - "component": "section", - "title": "{{ first(frame[\"benchmark_name\"]) }}", - "components": [ - { - "component": "table_aggregate", - "partition": ["subject_name", "variant_name"], - "groups": - { - "time (kde mode)": - { - "cols": ["time"] - }, - "memory": - { - "cols": ["memory"] - } - }, - "row": - { - "subject": "first(partition[\"subject_name\"]) ~ \" (\" ~ first(partition[\"variant_name\"]) ~ \")\"", - "time": - { - "type": "expand", - "partition": "suite_tag", - "cols": - { - "Tag: {{ key }}": "mode(partition[\"result_time_avg\"]) as time ~ ' (' ~ rstdev(partition['result_time_avg']) ~ ')'" - } - }, - "memory": - { - "type": "expand", - "partition": "suite_tag", - "cols": - { - "Tag: {{ key }} ": "mode(partition[\"result_mem_peak\"]) as memory" - } - } - } - }] - }] + "generator": "expression", + "break": [ + "benchmark" + ], + "cols": [ + "subject", + "revs", + "its", + "memory", + "base", + "current", + "diff" + ], + "expressions": { + "memory": "if(first(has_baseline), (first(result_mem_peak) as memory) ~ ' ' ~ percent_diff(first(baseline_mem_peak), first(result_mem_peak)), first(result_mem_peak) as memory)", + "base": "if(first(has_baseline), mode(baseline_time_avg) as time ~ ' (' ~ rstdev(baseline_time_avg) ~ ')', '')", + "current": "mode(result_time_avg) as time ~ ' (' ~ rstdev(result_time_avg) ~ ')'", + "diff": "if(first(has_baseline), percent_diff(mode(baseline_time_avg), mode(result_time_avg), rstdev(result_time_avg)), '')" + } } } -} \ No newline at end of file +} diff --git a/src/Metadata/CacheKey.php b/src/Metadata/CacheKey.php new file mode 100644 index 00000000..95f07492 --- /dev/null +++ b/src/Metadata/CacheKey.php @@ -0,0 +1,22 @@ +reflection = new ReflectionClass($data['className']); + /** @var class-string $className */ + $className = $data['className']; + + $this->reflection = new ReflectionClass($className); + $this->className = $className; $this->normalizer = $data['normalizer']; $this->properties = $data['properties']; $this->lazy = $data['lazy']; diff --git a/src/Metadata/Psr16MetadataFactory.php b/src/Metadata/Psr16MetadataFactory.php index 603e674f..f8ec5901 100644 --- a/src/Metadata/Psr16MetadataFactory.php +++ b/src/Metadata/Psr16MetadataFactory.php @@ -23,16 +23,15 @@ public function __construct( */ public function metadata(string $class): ClassMetadata { - /** @var ?ClassMetadata $metadata */ - $metadata = $this->cache->get($class); + $metadata = $this->cache->get(CacheKey::forClass($class)); - if ($metadata !== null) { + if ($metadata instanceof ClassMetadata && $metadata->className === $class) { return $metadata; } $metadata = $this->metadataFactory->metadata($class); - $this->cache->set($class, $metadata); + $this->cache->set(CacheKey::forClass($class), $metadata); return $metadata; } diff --git a/src/Metadata/Psr6MetadataFactory.php b/src/Metadata/Psr6MetadataFactory.php index f2cf0b5a..907eca1e 100644 --- a/src/Metadata/Psr6MetadataFactory.php +++ b/src/Metadata/Psr6MetadataFactory.php @@ -23,13 +23,14 @@ public function __construct( */ public function metadata(string $class): ClassMetadata { - $item = $this->cache->getItem($class); + $item = $this->cache->getItem(CacheKey::forClass($class)); if ($item->isHit()) { - /** @var ClassMetadata $data */ $data = $item->get(); - return $data; + if ($data instanceof ClassMetadata && $data->className === $class) { + return $data; + } } $metadata = $this->metadataFactory->metadata($class); diff --git a/tests/Benchmark/GeneratedHydratorBench.php b/tests/Benchmark/GeneratedHydratorBench.php index e51b3278..17da5b5d 100644 --- a/tests/Benchmark/GeneratedHydratorBench.php +++ b/tests/Benchmark/GeneratedHydratorBench.php @@ -45,7 +45,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -58,7 +58,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -104,36 +104,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php index cc691d08..7febf333 100644 --- a/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php +++ b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php @@ -55,7 +55,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate( @@ -71,7 +71,7 @@ public function benchHydrate1Object(): void ); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -120,39 +120,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/StackHydratorBench.php b/tests/Benchmark/StackHydratorBench.php index 7a7fe0e1..9d658951 100644 --- a/tests/Benchmark/StackHydratorBench.php +++ b/tests/Benchmark/StackHydratorBench.php @@ -39,7 +39,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -52,7 +52,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -98,36 +98,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/StackHydratorWithCryptographyBench.php b/tests/Benchmark/StackHydratorWithCryptographyBench.php index 965e40f8..8b2e589b 100644 --- a/tests/Benchmark/StackHydratorWithCryptographyBench.php +++ b/tests/Benchmark/StackHydratorWithCryptographyBench.php @@ -49,7 +49,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate( @@ -65,7 +65,7 @@ public function benchHydrate1Object(): void ); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -114,39 +114,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/HydratorWithLazyBench.php b/tests/Benchmark/StackHydratorWithLazyBench.php similarity index 71% rename from tests/Benchmark/HydratorWithLazyBench.php rename to tests/Benchmark/StackHydratorWithLazyBench.php index 19cc97ef..eb0a3ff6 100644 --- a/tests/Benchmark/HydratorWithLazyBench.php +++ b/tests/Benchmark/StackHydratorWithLazyBench.php @@ -10,7 +10,7 @@ use PhpBench\Attributes as Bench; #[Bench\BeforeMethods('setUp')] -final class HydratorWithLazyBench +final class StackHydratorWithLazyBench { private Hydrator $hydrator; @@ -38,7 +38,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -51,7 +51,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1ObjectTriggerInit(): void { $object = $this->hydrator->hydrate(ProfileCreated::class, [ @@ -97,36 +97,4 @@ public function benchHydrate1000ObjectsTriggerInit(): void $name = $object->name; } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000ObjectsTriggerInit(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $object = $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - - $object = $object->name; - } - } } diff --git a/tests/Unit/Metadata/CacheKeyTest.php b/tests/Unit/Metadata/CacheKeyTest.php new file mode 100644 index 00000000..42d6476b --- /dev/null +++ b/tests/Unit/Metadata/CacheKeyTest.php @@ -0,0 +1,22 @@ +properties['foo']); self::assertSame($barMetadata, $classMetadata->properties['bar']); } + + public function testSerialize(): void + { + $classMetadata = (new AttributeMetadataFactory())->metadata(ProfileCreated::class); + + $unserialized = unserialize(serialize($classMetadata)); + + self::assertInstanceOf(ClassMetadata::class, $unserialized); + self::assertSame(ProfileCreated::class, $unserialized->className); + self::assertEquals($classMetadata, $unserialized); + } } diff --git a/tests/Unit/Metadata/PropertyMetadataTest.php b/tests/Unit/Metadata/PropertyMetadataTest.php new file mode 100644 index 00000000..78910607 --- /dev/null +++ b/tests/Unit/Metadata/PropertyMetadataTest.php @@ -0,0 +1,38 @@ + 'bar'], + ); + + $unserialized = unserialize(serialize($propertyMetadata)); + + self::assertInstanceOf(PropertyMetadata::class, $unserialized); + self::assertSame('email', $unserialized->propertyName); + self::assertEquals($propertyMetadata, $unserialized); + } +} diff --git a/tests/Unit/Metadata/Psr16MetadataFactoryTest.php b/tests/Unit/Metadata/Psr16MetadataFactoryTest.php index 6f356bc8..4fd5740a 100644 --- a/tests/Unit/Metadata/Psr16MetadataFactoryTest.php +++ b/tests/Unit/Metadata/Psr16MetadataFactoryTest.php @@ -4,14 +4,19 @@ namespace Patchlevel\Hydrator\Tests\Unit\Metadata; +use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory; +use Patchlevel\Hydrator\Metadata\CacheKey; use Patchlevel\Hydrator\Metadata\ClassMetadata; use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr16MetadataFactory; +use Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Psr\SimpleCache\CacheInterface; use ReflectionClass; use stdClass; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Psr16Cache; #[CoversClass(Psr16MetadataFactory::class)] final class Psr16MetadataFactoryTest extends TestCase @@ -23,7 +28,7 @@ public function testMetadataWithHit(): void $cache = $this->createMock(CacheInterface::class); $cache->expects(self::once()) ->method('get') - ->with(stdClass::class) + ->with(CacheKey::forClass(stdClass::class)) ->willReturn($classMetadata); $innerFactory = $this->createMock(MetadataFactory::class); @@ -43,11 +48,37 @@ public function testMetadataWithMiss(): void $cache = $this->createMock(CacheInterface::class); $cache->expects(self::once()) ->method('get') - ->with(stdClass::class) + ->with(CacheKey::forClass(stdClass::class)) ->willReturn(null); $cache->expects(self::once()) ->method('set') - ->with(stdClass::class, $classMetadata); + ->with(CacheKey::forClass(stdClass::class), $classMetadata); + + $innerFactory = $this->createMock(MetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') + ->with(stdClass::class) + ->willReturn($classMetadata); + + $factory = new Psr16MetadataFactory($innerFactory, $cache); + $result = $factory->metadata(stdClass::class); + + self::assertSame($classMetadata, $result); + } + + public function testMetadataIgnoresEntryOfOtherClass(): void + { + $otherMetadata = new ClassMetadata(new ReflectionClass(ProfileCreated::class)); + $classMetadata = new ClassMetadata(new ReflectionClass(stdClass::class)); + + $cache = $this->createMock(CacheInterface::class); + $cache->expects(self::once()) + ->method('get') + ->with(CacheKey::forClass(stdClass::class)) + ->willReturn($otherMetadata); + $cache->expects(self::once()) + ->method('set') + ->with(CacheKey::forClass(stdClass::class), $classMetadata); $innerFactory = $this->createMock(MetadataFactory::class); $innerFactory->expects(self::once()) @@ -60,4 +91,16 @@ public function testMetadataWithMiss(): void self::assertSame($classMetadata, $result); } + + public function testMetadataWithSymfonyCache(): void + { + $cache = new Psr16Cache(new ArrayAdapter()); + $factory = new Psr16MetadataFactory(new AttributeMetadataFactory(), $cache); + + $metadata = $factory->metadata(ProfileCreated::class); + + self::assertSame(ProfileCreated::class, $metadata->className); + self::assertTrue($cache->has(CacheKey::forClass(ProfileCreated::class))); + self::assertEquals($metadata, $factory->metadata(ProfileCreated::class)); + } } diff --git a/tests/Unit/Metadata/Psr6MetadataFactoryTest.php b/tests/Unit/Metadata/Psr6MetadataFactoryTest.php index 0d6d5f24..e72762fa 100644 --- a/tests/Unit/Metadata/Psr6MetadataFactoryTest.php +++ b/tests/Unit/Metadata/Psr6MetadataFactoryTest.php @@ -4,15 +4,19 @@ namespace Patchlevel\Hydrator\Tests\Unit\Metadata; +use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory; +use Patchlevel\Hydrator\Metadata\CacheKey; use Patchlevel\Hydrator\Metadata\ClassMetadata; use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr6MetadataFactory; +use Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; use stdClass; +use Symfony\Component\Cache\Adapter\ArrayAdapter; #[CoversClass(Psr6MetadataFactory::class)] final class Psr6MetadataFactoryTest extends TestCase @@ -32,7 +36,7 @@ public function testMetadataWithHit(): void $cache = $this->createMock(CacheItemPoolInterface::class); $cache->expects(self::once()) ->method('getItem') - ->with(stdClass::class) + ->with(CacheKey::forClass(stdClass::class)) ->willReturn($item); $innerFactory = $this->createMock(MetadataFactory::class); @@ -60,7 +64,44 @@ public function testMetadataWithMiss(): void $cache = $this->createMock(CacheItemPoolInterface::class); $cache->expects(self::once()) ->method('getItem') + ->with(CacheKey::forClass(stdClass::class)) + ->willReturn($item); + $cache->expects(self::once()) + ->method('save') + ->with($item); + + $innerFactory = $this->createMock(MetadataFactory::class); + $innerFactory->expects(self::once()) + ->method('metadata') ->with(stdClass::class) + ->willReturn($classMetadata); + + $factory = new Psr6MetadataFactory($innerFactory, $cache); + $result = $factory->metadata(stdClass::class); + + self::assertSame($classMetadata, $result); + } + + public function testMetadataIgnoresEntryOfOtherClass(): void + { + $otherMetadata = new ClassMetadata(new ReflectionClass(ProfileCreated::class)); + $classMetadata = new ClassMetadata(new ReflectionClass(stdClass::class)); + + $item = $this->createMock(CacheItemInterface::class); + $item->expects(self::once()) + ->method('isHit') + ->willReturn(true); + $item->expects(self::once()) + ->method('get') + ->willReturn($otherMetadata); + $item->expects(self::once()) + ->method('set') + ->with($classMetadata); + + $cache = $this->createMock(CacheItemPoolInterface::class); + $cache->expects(self::once()) + ->method('getItem') + ->with(CacheKey::forClass(stdClass::class)) ->willReturn($item); $cache->expects(self::once()) ->method('save') @@ -77,4 +118,16 @@ public function testMetadataWithMiss(): void self::assertSame($classMetadata, $result); } + + public function testMetadataWithSymfonyCache(): void + { + $cache = new ArrayAdapter(); + $factory = new Psr6MetadataFactory(new AttributeMetadataFactory(), $cache); + + $metadata = $factory->metadata(ProfileCreated::class); + + self::assertSame(ProfileCreated::class, $metadata->className); + self::assertTrue($cache->hasItem(CacheKey::forClass(ProfileCreated::class))); + self::assertEquals($metadata, $factory->metadata(ProfileCreated::class)); + } }