diff --git a/composer.json b/composer.json index eea20dc1..b670ee78 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-phpunit": "^2.0.13", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "psr/container": "1.1.2", diff --git a/extension.neon b/extension.neon index 19cceefb..04b4bdca 100644 --- a/extension.neon +++ b/extension.neon @@ -311,9 +311,14 @@ services: tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension - arguments: - tmpDir: %tmpDir% - containerXmlPath: %symfony.containerXmlPath% + class: PHPStan\Symfony\ServiceResultCacheValueExtension + tags: + - phpstan.resultCacheValueExtension + - + class: PHPStan\Symfony\ParameterResultCacheValueExtension + tags: + - phpstan.resultCacheValueExtension + - + class: PHPStan\Symfony\MessageResultCacheValueExtension tags: - - phpstan.resultCacheMetaExtension + - phpstan.resultCacheValueExtension diff --git a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php index c8ec11a8..cc5120a8 100644 --- a/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php @@ -4,10 +4,12 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Symfony\ServiceMap; +use PHPStan\Symfony\ServiceResultCacheValueExtension; use PHPStan\TrinaryLogic; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -31,6 +33,9 @@ public function getNodeType(): string return MethodCall::class; } + /** + * @param Scope&DependencyEmitter $scope + */ public function processNode(Node $node, Scope $scope): array { if (!$node->name instanceof Node\Identifier) { @@ -66,6 +71,7 @@ public function processNode(Node $node, Scope $scope): array $serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $scope->valueDependency(ServiceResultCacheValueExtension::class, $serviceId); $service = $this->serviceMap->getService($serviceId); if ($service !== null && !$service->isPublic()) { return [ diff --git a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php index 23444b6b..128dcc5d 100644 --- a/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php +++ b/src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php @@ -4,11 +4,13 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\Node\Printer\Printer; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Symfony\ServiceMap; +use PHPStan\Symfony\ServiceResultCacheValueExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Symfony\Helper; use function sprintf; @@ -34,6 +36,9 @@ public function getNodeType(): string return MethodCall::class; } + /** + * @param Scope&DependencyEmitter $scope + */ public function processNode(Node $node, Scope $scope): array { if (!$node->name instanceof Node\Identifier) { @@ -65,6 +70,7 @@ public function processNode(Node $node, Scope $scope): array $serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $scope->valueDependency(ServiceResultCacheValueExtension::class, $serviceId); $service = $this->serviceMap->getService($serviceId); $serviceIdType = $scope->getType($node->getArgs()[0]->value); if ($service === null && !$scope->getType(Helper::createMarkerNode($node->var, $serviceIdType, $this->printer))->equals($serviceIdType)) { diff --git a/src/Symfony/MessageResultCacheValueExtension.php b/src/Symfony/MessageResultCacheValueExtension.php new file mode 100644 index 00000000..162c6dbc --- /dev/null +++ b/src/Symfony/MessageResultCacheValueExtension.php @@ -0,0 +1,48 @@ +messageMapFactory = $symfonyMessageMapFactory; + } + + public function getValue(string $key): string + { + if ($this->messageMap === null) { + $this->messageMap = $this->messageMapFactory->create(); + } + + $type = $this->messageMap->getTypeForClass($key); + if ($type === null) { + return 'none'; + } + + return $type->describe(VerbosityLevel::precise()); + } + + public function keyToResultCache(string $key): string + { + return $key; + } + + public function keyFromResultCache(string $storedKey): string + { + return $storedKey; + } + +} diff --git a/src/Symfony/ParameterResultCacheValueExtension.php b/src/Symfony/ParameterResultCacheValueExtension.php new file mode 100644 index 00000000..d4dcc684 --- /dev/null +++ b/src/Symfony/ParameterResultCacheValueExtension.php @@ -0,0 +1,42 @@ +parameterMap = $symfonyParameterMap; + } + + public function getValue(string $key): string + { + $parameter = $this->parameterMap->getParameter($key); + if ($parameter === null) { + return 'missing'; + } + + return hash('sha256', var_export($parameter->getValue(), true)); + } + + public function keyToResultCache(string $key): string + { + return $key; + } + + public function keyFromResultCache(string $storedKey): string + { + return $storedKey; + } + +} diff --git a/src/Symfony/ServiceResultCacheValueExtension.php b/src/Symfony/ServiceResultCacheValueExtension.php new file mode 100644 index 00000000..bf8cd736 --- /dev/null +++ b/src/Symfony/ServiceResultCacheValueExtension.php @@ -0,0 +1,73 @@ +serviceMap = $symfonyServiceMap; + $this->parameterMap = $symfonyParameterMap; + } + + public function getValue(string $key): string + { + $service = $this->serviceMap->getService($key); + if ($service === null) { + return 'missing'; + } + + $class = $service->getClass(); + + return hash('sha256', var_export([ + 'class' => $class, + 'resolvedClass' => $class !== null && strpos($class, '%') !== false ? $this->resolveClass($class) : null, + 'public' => $service->isPublic(), + 'synthetic' => $service->isSynthetic(), + ], true)); + } + + public function keyToResultCache(string $key): string + { + return $key; + } + + public function keyFromResultCache(string $storedKey): string + { + return $storedKey; + } + + /** + * @return mixed + */ + private function resolveClass(string $class) + { + if (!class_exists(ParameterBag::class)) { + return null; + } + + $parameters = []; + foreach ($this->parameterMap->getParameters() as $parameter) { + $parameters[$parameter->getKey()] = $parameter->getValue(); + } + + return (new ParameterBag($parameters))->resolveValue($class); + } + +} diff --git a/src/Symfony/SymfonyContainerResultCacheMetaExtension.php b/src/Symfony/SymfonyContainerResultCacheMetaExtension.php deleted file mode 100644 index 70a633fd..00000000 --- a/src/Symfony/SymfonyContainerResultCacheMetaExtension.php +++ /dev/null @@ -1,100 +0,0 @@ -parameterMap = $parameterMap; - $this->serviceMap = $serviceMap; - $this->tmpDir = $tmpDir; - $this->containerXmlPath = $containerXmlPath; - } - - public function getKey(): string - { - return 'symfonyDiContainer'; - } - - public function getHash(): string - { - if ($this->containerXmlPath !== null) { - $xmlHash = hash_file('sha256', $this->containerXmlPath); - if ($xmlHash === false) { - throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXmlPath)); - } - $xmlHashFile = sprintf('%s/%s-%s.hash', $this->tmpDir, $this->getKey(), basename($this->containerXmlPath)); - $storedXmlHash = @file_get_contents($xmlHashFile); - - $hashForResultCacheFile = sprintf('%s/%s-%s-result-cache-meta.hash', $this->tmpDir, $this->getKey(), basename($this->containerXmlPath)); - $storedHashForResultCache = @file_get_contents($hashForResultCacheFile); - if ($storedXmlHash === $xmlHash && $storedHashForResultCache !== false) { - return $storedHashForResultCache; - } - } - - $hashForResultCache = $this->calculateHash(); - - if ($this->containerXmlPath !== null) { - file_put_contents($hashForResultCacheFile, $hashForResultCache); - file_put_contents($xmlHashFile, $xmlHash); - } - - return $hashForResultCache; - } - - private function calculateHash(): string - { - $services = $parameters = []; - - foreach ($this->parameterMap->getParameters() as $parameter) { - $parameters[$parameter->getKey()] = $parameter->getValue(); - } - ksort($parameters); - - foreach ($this->serviceMap->getServices() as $service) { - $serviceTags = array_map( - static fn (ServiceTag $tag) => [ - 'name' => $tag->getName(), - 'attributes' => $tag->getAttributes(), - ], - $service->getTags(), - ); - sort($serviceTags); - - $services[$service->getId()] = [ - 'class' => $service->getClass(), - 'public' => $service->isPublic() ? 'yes' : 'no', - 'synthetic' => $service->isSynthetic() ? 'yes' : 'no', - 'alias' => $service->getAlias(), - 'tags' => $serviceTags, - ]; - } - ksort($services); - - return hash('sha256', var_export(['parameters' => $parameters, 'services' => $services], true)); - } - -} diff --git a/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php b/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php index a7685b99..fb50b097 100644 --- a/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php +++ b/src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php @@ -5,9 +5,11 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Identifier; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\Symfony\MessageMap; use PHPStan\Symfony\MessageMapFactory; +use PHPStan\Symfony\MessageResultCacheValueExtension; use PHPStan\Type\ExpressionTypeResolverExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -29,6 +31,9 @@ public function __construct(MessageMapFactory $symfonyMessageMapFactory) $this->messageMapFactory = $symfonyMessageMapFactory; } + /** + * @param Scope&DependencyEmitter $scope + */ public function getType(Expr $expr, Scope $scope): ?Type { if (!$this->isSupported($expr, $scope)) { @@ -51,6 +56,7 @@ public function getType(Expr $expr, Scope $scope): ?Type $returnTypes = []; foreach ($argClassNames as $argClassName) { + $scope->valueDependency(MessageResultCacheValueExtension::class, $argClassName); $returnType = $messageMap->getTypeForClass($argClassName); if (is_null($returnType)) { diff --git a/src/Type/Symfony/MessengerHandleTraitWrapperReturnTypeExtension.php b/src/Type/Symfony/MessengerHandleTraitWrapperReturnTypeExtension.php index fa0c8862..1e259ed6 100644 --- a/src/Type/Symfony/MessengerHandleTraitWrapperReturnTypeExtension.php +++ b/src/Type/Symfony/MessengerHandleTraitWrapperReturnTypeExtension.php @@ -5,10 +5,12 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Identifier; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Symfony\MessageMap; use PHPStan\Symfony\MessageMapFactory; +use PHPStan\Symfony\MessageResultCacheValueExtension; use PHPStan\Type\ExpressionTypeResolverExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -46,6 +48,9 @@ public function __construct(MessageMapFactory $messageMapFactory, ?array $messen $this->reflectionProvider = $reflectionProvider; } + /** + * @param Scope&DependencyEmitter $scope + */ public function getType(Expr $expr, Scope $scope): ?Type { if (!$this->isSupported($expr, $scope)) { @@ -67,6 +72,7 @@ public function getType(Expr $expr, Scope $scope): ?Type $returnTypes = []; foreach ($argClassNames as $argClassName) { $messageMap = $this->getMessageMap(); + $scope->valueDependency(MessageResultCacheValueExtension::class, $argClassName); $returnType = $messageMap->getTypeForClass($argClassName); if (is_null($returnType)) { diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 618e0f6b..70187b82 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -3,11 +3,13 @@ namespace PHPStan\Type\Symfony; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\MethodReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Symfony\ParameterMap; +use PHPStan\Symfony\ParameterResultCacheValueExtension; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantArrayType; @@ -86,6 +88,9 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return in_array($methodReflection->getName(), $methods, true); } + /** + * @param Scope&DependencyEmitter $scope + */ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { switch ($methodReflection->getName()) { @@ -97,6 +102,9 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method throw new ShouldNotHappenException(); } + /** + * @param Scope&DependencyEmitter $scope + */ private function getGetTypeFromMethodCall( MethodCall $methodCall, Scope $scope @@ -123,6 +131,7 @@ private function getGetTypeFromMethodCall( $returnTypes = []; foreach ($parameterKeys as $parameterKey) { + $scope->valueDependency(ParameterResultCacheValueExtension::class, $parameterKey); $parameter = $this->parameterMap->getParameter($parameterKey); if ($parameter === null) { return $defaultReturnType; @@ -202,6 +211,9 @@ private function generalizeType(Type $type): Type }); } + /** + * @param Scope&DependencyEmitter $scope + */ private function getHasTypeFromMethodCall( MethodCall $methodCall, Scope $scope @@ -218,6 +230,7 @@ private function getHasTypeFromMethodCall( $has = null; foreach ($parameterKeys as $parameterKey) { + $scope->valueDependency(ParameterResultCacheValueExtension::class, $parameterKey); $parameter = $this->parameterMap->getParameter($parameterKey); if ($has === null) { diff --git a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php index b9c23f91..cf28430b 100644 --- a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php @@ -3,12 +3,14 @@ namespace PHPStan\Type\Symfony; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\DependencyEmitter; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Symfony\ParameterMap; use PHPStan\Symfony\ServiceDefinition; use PHPStan\Symfony\ServiceMap; +use PHPStan\Symfony\ServiceResultCacheValueExtension; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\ObjectType; @@ -58,6 +60,9 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return in_array($methodReflection->getName(), ['get', 'has'], true); } + /** + * @param Scope&DependencyEmitter $scope + */ public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { switch ($methodReflection->getName()) { @@ -69,6 +74,9 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method throw new ShouldNotHappenException(); } + /** + * @param Scope&DependencyEmitter $scope + */ private function getGetTypeFromMethodCall( MethodCall $methodCall, Scope $scope @@ -85,6 +93,7 @@ private function getGetTypeFromMethodCall( $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $scope->valueDependency(ServiceResultCacheValueExtension::class, $serviceId); $service = $this->serviceMap->getService($serviceId); if ($service !== null && (!$service->isSynthetic() || $service->getClass() !== null)) { return new ObjectType($this->determineServiceClass($parameterBag, $service) ?? $serviceId); @@ -118,6 +127,9 @@ private function tryCreateParameterBag(): ?ParameterBag return new ParameterBag($parameters); } + /** + * @param Scope&DependencyEmitter $scope + */ private function getHasTypeFromMethodCall( MethodCall $methodCall, Scope $scope @@ -129,6 +141,7 @@ private function getHasTypeFromMethodCall( $serviceId = $this->serviceMap::getServiceIdFromNode($methodCall->getArgs()[0]->value, $scope); if ($serviceId !== null) { + $scope->valueDependency(ServiceResultCacheValueExtension::class, $serviceId); $service = $this->serviceMap->getService($serviceId); return new ConstantBooleanType($service !== null && $service->isPublic()); } diff --git a/tests/Symfony/ResultCacheValueExtensionsTest.php b/tests/Symfony/ResultCacheValueExtensionsTest.php new file mode 100644 index 00000000..85bf4f24 --- /dev/null +++ b/tests/Symfony/ResultCacheValueExtensionsTest.php @@ -0,0 +1,104 @@ +getValue('Foo'); + + // what the other services are, and in which order, is not part of it + self::assertSame($value, (new ServiceResultCacheValueExtension(self::services([ + new Service('Bar', 'BarAdapter', false, true, null), + new Service('Baz', 'Baz', true, false, null), + new Service('Foo', 'Foo', true, false, null, [new ServiceTag('foo.bar', ['baz' => 'bar'])]), + ]), self::parameters([new Parameter('foo', 'foo')])))->getValue('Foo')); + + foreach ([ + 'class' => new Service('Foo', 'FooAdapter', true, false, null), + 'visibility' => new Service('Foo', 'Foo', false, false, null), + 'syntheticity' => new Service('Foo', 'Foo', true, true, null), + ] as $change => $service) { + self::assertNotSame($value, (new ServiceResultCacheValueExtension(self::services([$service]), self::parameters([])))->getValue('Foo'), $change); + } + + self::assertSame('missing', (new ServiceResultCacheValueExtension(self::services([]), self::parameters([])))->getValue('Foo')); + } + + public function testServiceClassFromParameter(): void + { + $serviceMap = self::services([new Service('Foo', '%foo.class%', true, false, null)]); + $value = (new ServiceResultCacheValueExtension($serviceMap, self::parameters([ + new Parameter('foo.class', 'Foo'), + ])))->getValue('Foo'); + + self::assertNotSame($value, (new ServiceResultCacheValueExtension($serviceMap, self::parameters([ + new Parameter('foo.class', 'FooAdapter'), + ])))->getValue('Foo')); + } + + public function testParameter(): void + { + $value = (new ParameterResultCacheValueExtension(self::parameters([ + new Parameter('foo', ['a' => 1]), + new Parameter('bar', 'bar'), + ])))->getValue('foo'); + + self::assertSame($value, (new ParameterResultCacheValueExtension(self::parameters([ + new Parameter('bar', 'buzz'), + new Parameter('foo', ['a' => 1]), + ])))->getValue('foo')); + self::assertNotSame($value, (new ParameterResultCacheValueExtension(self::parameters([ + new Parameter('foo', ['a' => 2]), + ])))->getValue('foo')); + self::assertSame('missing', (new ParameterResultCacheValueExtension(self::parameters([])))->getValue('foo')); + } + + public function testMessage(): void + { + $reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class); + $extension = new MessageResultCacheValueExtension(new MessageMapFactory(self::services([ + new Service('tagged_handler', 'MessengerHandleTrait\TaggedHandler', true, false, null, [ + new ServiceTag('messenger.message_handler', ['handles' => 'MessengerHandleTrait\TaggedQuery', 'method' => 'handle']), + ]), + ]), $reflectionProvider)); + + self::assertSame('MessengerHandleTrait\TaggedResult', $extension->getValue('MessengerHandleTrait\TaggedQuery')); + self::assertSame('none', $extension->getValue('MessengerHandleTrait\RegularQuery')); + } + + /** + * @param list $services + */ + private static function services(array $services): DefaultServiceMap + { + $map = []; + foreach ($services as $service) { + $map[$service->getId()] = $service; + } + + return new DefaultServiceMap($map); + } + + /** + * @param list $parameters + */ + private static function parameters(array $parameters): DefaultParameterMap + { + $map = []; + foreach ($parameters as $parameter) { + $map[$parameter->getKey()] = $parameter; + } + + return new DefaultParameterMap($map); + } + +} diff --git a/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php b/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php deleted file mode 100644 index 1cf9276c..00000000 --- a/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php +++ /dev/null @@ -1,368 +0,0 @@ -tmpDir = sys_get_temp_dir() . '/phpstan-symfony-test-' . uniqid('', true); - mkdir($this->tmpDir, 0777, true); - } - - protected function tearDown(): void - { - $cacheFiles = glob($this->tmpDir . '/*.hash'); - if ($cacheFiles !== false) { - foreach ($cacheFiles as $file) { - unlink($file); - } - } - rmdir($this->tmpDir); - parent::tearDown(); - } - - public function testHashIsCalculatedAndWrittenToCacheFileOnCacheMiss(): void - { - $containerXmlPath = __DIR__ . '/container.xml'; - - $extension = new SymfonyContainerResultCacheMetaExtension( - new DefaultParameterMap([]), - new DefaultServiceMap([]), - $this->tmpDir, - $containerXmlPath, - ); - - $hash = $extension->getHash(); - - $resultCacheHashFile = sprintf('%s/symfonyDiContainer-container.xml-result-cache-meta.hash', $this->tmpDir); - self::assertFileExists($resultCacheHashFile); - self::assertSame($hash, file_get_contents($resultCacheHashFile)); - - $xmlHashFile = sprintf('%s/symfonyDiContainer-container.xml.hash', $this->tmpDir); - self::assertFileExists($xmlHashFile); - self::assertSame('c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f', file_get_contents($xmlHashFile)); - } - - public function testCachedHashIsReturnedOnCacheHit(): void - { - $containerXmlPath = __DIR__ . '/container.xml'; - $xmlHashFile = sprintf('%s/symfonyDiContainer-container.xml.hash', $this->tmpDir); - file_put_contents($xmlHashFile, 'c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f'); - $resultCacheHashFile = sprintf('%s/symfonyDiContainer-container.xml-result-cache-meta.hash', $this->tmpDir); - file_put_contents($resultCacheHashFile, 'pre-computed-hash'); - - $extension = new SymfonyContainerResultCacheMetaExtension( - new DefaultParameterMap([]), - new DefaultServiceMap([]), - $this->tmpDir, - $containerXmlPath, - ); - - self::assertSame('pre-computed-hash', $extension->getHash()); - } - - /** - * @param list $sameHashContents - * @param ContainerContents $invalidatingContent - * - * @dataProvider provideContainerHashIsCalculatedCorrectlyCases - */ - public function testContainerHashIsCalculatedCorrectly( - array $sameHashContents, - array $invalidatingContent - ): void - { - $hash = null; - - self::assertGreaterThan(0, count($sameHashContents)); - - foreach ($sameHashContents as $content) { - $currentHash = (new SymfonyContainerResultCacheMetaExtension( - $content['parameters'] ?? new DefaultParameterMap([]), - $content['services'] ?? new DefaultServiceMap([]), - __DIR__ . '/../../tmp', - null, - ))->getHash(); - - if ($hash === null) { - $hash = $currentHash; - } else { - self::assertSame($hash, $currentHash); - } - } - - self::assertNotSame( - $hash, - (new SymfonyContainerResultCacheMetaExtension( - $invalidatingContent['parameters'] ?? new DefaultParameterMap([]), - $invalidatingContent['services'] ?? new DefaultServiceMap([]), - __DIR__ . '/../../tmp', - null, - ))->getHash(), - ); - } - - /** - * @return iterable, ContainerContents}> - */ - public static function provideContainerHashIsCalculatedCorrectlyCases(): iterable - { - yield 'service "class" changes' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'Bar', true, false, null), - ]), - ], - // Swapping services order in XML file does not affect the calculated hash - [ - 'services' => new DefaultServiceMap([ - new Service('Bar', 'Bar', true, false, null), - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'BarAdapter', true, false, null), - ]), - ], - ]; - - yield 'service visibility changes' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', false, false, null), - ]), - ], - ]; - - yield 'service syntheticity changes' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, true, null), - ]), - ], - ]; - - yield 'service alias changes' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'Bar', true, false, null), - new Service('Baz', null, true, false, 'Foo'), - ]), - ], - // Swapping services order in XML file does not affect the calculated hash - [ - 'services' => new DefaultServiceMap([ - new Service('Baz', null, true, false, 'Foo'), - new Service('Bar', 'Bar', true, false, null), - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'Bar', true, false, null), - new Service('Baz', null, true, false, 'Bar'), - ]), - ], - ]; - - yield 'service tag attributes changes' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - new ServiceTag('foo.baz', ['baz' => 'baz']), - ]), - ]), - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.baz', ['baz' => 'baz']), - new ServiceTag('foo.bar', ['baz' => 'bar']), - ]), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - new ServiceTag('foo.baz', ['baz' => 'buzz']), - ]), - ]), - ], - ]; - - yield 'service tag added' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - ]), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - new ServiceTag('foo.baz', ['baz' => 'baz']), - ]), - ]), - ], - ]; - - yield 'service tag removed' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - new ServiceTag('foo.baz', ['baz' => 'baz']), - ]), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null, [ - new ServiceTag('foo.bar', ['baz' => 'bar']), - ]), - ]), - ], - ]; - - yield 'new service added' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'Bar', true, false, null), - ]), - ], - ]; - - yield 'service removed' => [ - [ - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - new Service('Bar', 'Bar', true, false, null), - ]), - ], - ], - [ - 'services' => new DefaultServiceMap([ - new Service('Foo', 'Foo', true, false, null), - ]), - ], - ]; - - yield 'parameter value changes' => [ - [ - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - new Parameter('bar', 'bar'), - ]), - ], - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('bar', 'bar'), - new Parameter('foo', 'foo'), - ]), - ], - ], - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - new Parameter('bar', 'buzz'), - ]), - ], - ]; - - yield 'new parameter added' => [ - [ - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - ]), - ], - ], - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - new Parameter('bar', 'bar'), - ]), - ], - ]; - - yield 'parameter removed' => [ - [ - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - new Parameter('bar', 'bar'), - ]), - ], - ], - [ - 'parameters' => new DefaultParameterMap([ - new Parameter('foo', 'foo'), - ]), - ], - ]; - } - -}