From 49555ef0e10deab2ba07d888002cec06320ce71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 21 Feb 2024 14:57:26 +0100 Subject: [PATCH 01/15] Generate Access Interceptor Proxy --- .../Symfony/CacheWarmer/ProxyCacheWarmer.php | 44 ++++++ .../Symfony/Config/services.php | 7 + .../Compiler/ServiceProxyPass.php | 23 +-- .../AccessInterceptorGenerator.php | 73 ++++++++++ .../Factory/AccessInterceptorFactory.php | 58 ++++++++ .../Method/InterceptedMethod.php | 9 +- .../Method/InterceptorGenerator.php | 88 ++++++++++++ .../Method/SetMethodPrefixInterceptors.php | 32 +++++ .../Method/SetMethodSuffixInterceptors.php | 35 +++++ .../AccessInterceptorValueHolderGenerator.php | 133 ------------------ .../AccessInterceptorValueHolderFactory.php | 87 ------------ src/Generator/Method/InterceptorGenerator.php | 99 ------------- src/Model/Request/Instance.php | 21 +-- src/Proxy/AccessInterceptorsInterface.php | 15 ++ src/ProxyFactory.php | 45 ++++-- .../Stub/Cache/ClassWithCacheAttributes.php | 7 + .../ClassWithInvalidateCacheAttributes.php | 2 + .../ClassWithAnnotationOnPrivateMethod.php | 14 ++ tests/Double/Stub/ClassWithFinalMethod.php | 14 ++ tests/Double/Stub/FinalClass.php | 11 ++ tests/Interceptor/AbstractInterceptorTest.php | 2 +- tests/Interceptor/CacheInterceptorTest.php | 47 ++++--- tests/Interceptor/EventInterceptorTest.php | 4 +- .../InvalidateCacheInterceptorTest.php | 2 +- .../LegacyCacheInterceptorTest.php | 4 +- tests/Interceptor/SecurityInterceptorTest.php | 2 +- tests/Interceptor/TestCodeTemplate.php | 5 +- tests/ProxyFactoryTest.php | 49 +++++-- tests/ProxyTestTrait.php | 21 +-- 29 files changed, 539 insertions(+), 414 deletions(-) create mode 100644 src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php create mode 100644 src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php create mode 100644 src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php rename src/Generator/{ => AccessInterceptorGenerator}/Method/InterceptedMethod.php (78%) create mode 100644 src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php create mode 100644 src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php create mode 100644 src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php delete mode 100644 src/Generator/AccessInterceptorValueHolderGenerator.php delete mode 100644 src/Generator/Factory/AccessInterceptorValueHolderFactory.php delete mode 100644 src/Generator/Method/InterceptorGenerator.php create mode 100644 src/Proxy/AccessInterceptorsInterface.php create mode 100644 tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php create mode 100644 tests/Double/Stub/ClassWithFinalMethod.php create mode 100644 tests/Double/Stub/FinalClass.php diff --git a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php new file mode 100644 index 0000000..f099d21 --- /dev/null +++ b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php @@ -0,0 +1,44 @@ +proxies = $proxies; + } + + /** + * {@inheritdoc} + */ + public function warmUp(string $cacheDir) + { + foreach ($this->proxies as $proxy) { + if ($proxy instanceof LazyLoadingInterface && !$proxy->isProxyInitialized()) { + $proxy->initializeProxy(); + } + + if (class_exists(LazyObjectInterface::class) + && $proxy instanceof LazyObjectInterface + && !$proxy->isLazyObjectInitialized()) { + $proxy->initializeLazyObject(); + } + } + } + + /** + * {@inheritdoc} + */ + public function isOptional() + { + return true; + } +} diff --git a/src/FrameworkBridge/Symfony/Config/services.php b/src/FrameworkBridge/Symfony/Config/services.php index df986c4..c910c39 100644 --- a/src/FrameworkBridge/Symfony/Config/services.php +++ b/src/FrameworkBridge/Symfony/Config/services.php @@ -10,6 +10,7 @@ use OpenClassrooms\ServiceProxy\ProxyFactory; use OpenClassrooms\ServiceProxy\ProxyFactoryConfiguration; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\CacheWarmer\ProxyCacheWarmer; use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service; use function Symfony\Component\DependencyInjection\Loader\Configurator\param; use function Symfony\Component\DependencyInjection\Loader\Configurator\service; @@ -76,6 +77,12 @@ ]) ->tag('kernel.event_subscriber'); + $services->set(ProxyCacheWarmer::class) + ->args([ + tagged_iterator('openclassrooms.service_proxy') + ]) + ->tag('kernel.cache_warmer', ['priority' => 128]); + $services->set(ServiceProxyEventFactory::class) ->autowire() ->autoconfigure(); diff --git a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php index 4c066c3..ad7bd3b 100644 --- a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php +++ b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php @@ -30,23 +30,26 @@ private function buildServiceProxies(): void $serviceProxyIds = []; $taggedServices = $this->container->findTaggedServiceIds('openclassrooms.service_proxy'); foreach ($taggedServices as $taggedServiceId => $tagParameters) { - $this->buildServiceProxyFactoryDefinition($taggedServiceId); + $this->overrideServiceDefinition($taggedServiceId); $serviceProxyIds[] = $taggedServiceId; - $this->compiler->log($this, "Add proxy for {$taggedServiceId} service."); + $this->compiler->log($this, "Override service definition for {$taggedServiceId} service."); } $this->container->setParameter('openclassrooms.service_proxy.service_proxy_ids', $serviceProxyIds); } - private function buildServiceProxyFactoryDefinition(string $taggedServiceName): void + private function overrideServiceDefinition(string $taggedServiceName): void { $definition = $this->container->findDefinition($taggedServiceName); - $factoryDefinition = new Definition($definition->getClass()); - $factoryDefinition->setFactory([new Reference(ProxyFactory::class), 'createProxy']); - $factoryDefinition->setArguments([$definition]); - $this->container->setDefinition($taggedServiceName, $factoryDefinition); - $factoryDefinition->setPublic($definition->isPublic()); - $factoryDefinition->setLazy($definition->isLazy()); - $factoryDefinition->setTags($definition->getTags()); + + if ($definition->getFactory() !== null) { + $this->compiler->log($this, "Service {$taggedServiceName} is not compatible with service proxy"); + + return; + } + + $definition->setFactory([new Reference(ProxyFactory::class), 'createInstance']); + + $definition->setArguments([$definition->getClass(), ...$definition->getArguments()]); } } diff --git a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php new file mode 100644 index 0000000..de34a3d --- /dev/null +++ b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php @@ -0,0 +1,73 @@ +} $proxyOptions + * + * @return void + * + * @throws \InvalidArgumentException + * @throws InvalidProxiedClassException + */ + public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) + { + if (!array_key_exists('methods', $proxyOptions)) { + throw new \InvalidArgumentException(sprintf("Generator %s needs a methods proxyOptions",__CLASS__)); + } + + CanProxyAssertion::assertClassCanBeProxied($originalClass, false); + + $classGenerator->setExtendedClass($originalClass->getName()); + $classGenerator->setImplementedInterfaces([AccessInterceptorsInterface::class]); + $classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors()); + $classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors()); + + array_map( + static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + $this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors), + array_map(static fn (string $method) => $originalClass->getMethod($method), $proxyOptions['methods']) + ), + [ + new SetMethodPrefixInterceptors($prefixInterceptors), + new SetMethodSuffixInterceptors($suffixInterceptors), + ] + ) + ); + } + + private function buildMethodInterceptor( + MethodPrefixInterceptors $prefixInterceptors, + MethodSuffixInterceptors $suffixInterceptors + ): callable { + return static function (\ReflectionMethod $method) use ($prefixInterceptors, $suffixInterceptors): InterceptedMethod { + return InterceptedMethod::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $prefixInterceptors, + $suffixInterceptors + ); + }; + } +} diff --git a/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php b/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php new file mode 100644 index 0000000..882d913 --- /dev/null +++ b/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php @@ -0,0 +1,58 @@ +generator = new AccessInterceptorGenerator(); + } + + /** + * @template T of object + * + * @param class-string $class + * @param array $args + * @param array $prefixInterceptors an array (indexed by method name) of interceptor closures to be called + * before method logic is executed + * @param array $suffixInterceptors an array (indexed by method name) of interceptor closures to be called + * after method logic is executed + * + * @return T + */ + public function createInstance( + string $class, + array $args, + array $prefixInterceptors = [], + array $suffixInterceptors = [] + ): object { + $methods = array_merge( + array_keys($prefixInterceptors), + array_keys($suffixInterceptors) + ); + $methods = array_unique($methods); + + $proxyClassName = $this->generateProxy($class, ['methods' => $methods]); + + $instance = new $proxyClassName(...$args); + + $instance->setPrefixInterceptors($prefixInterceptors); + $instance->setSuffixInterceptors($suffixInterceptors); + + return $instance; + } + + public function getGenerator(): AccessInterceptorGenerator + { + return $this->generator; + } +} diff --git a/src/Generator/Method/InterceptedMethod.php b/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php similarity index 78% rename from src/Generator/Method/InterceptedMethod.php rename to src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php index 33d1964..11772cf 100644 --- a/src/Generator/Method/InterceptedMethod.php +++ b/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace OpenClassrooms\ServiceProxy\Generator\Method; +namespace OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method; use Laminas\Code\Generator\Exception\InvalidArgumentException; use Laminas\Code\Generator\PropertyGenerator; use Laminas\Code\Reflection\MethodReflection; +use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method\InterceptorGenerator; use ProxyManager\Generator\MethodGenerator; /** @@ -20,11 +21,10 @@ final class InterceptedMethod extends MethodGenerator */ public static function generateMethod( MethodReflection $originalMethod, - PropertyGenerator $valueHolderProperty, PropertyGenerator $prefixInterceptors, PropertyGenerator $suffixInterceptors ): self { - $method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod); + $method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod); $forwardedParams = []; foreach ($originalMethod->getParameters() as $parameter) { @@ -32,10 +32,9 @@ public static function generateMethod( } $method->setBody(InterceptorGenerator::createInterceptedMethodBody( - '$returnValue = $this->' . $valueHolderProperty->getName() . '->' + '$returnValue = parent::' . $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');', $method, - $valueHolderProperty, $prefixInterceptors, $suffixInterceptors, $originalMethod diff --git a/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php new file mode 100644 index 0000000..af08101 --- /dev/null +++ b/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php @@ -0,0 +1,88 @@ +{{$prefixInterceptorsName}}[{{$name}}])) { + $returnEarly = false; + $prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $returnEarly); + + if ($returnEarly) { + {{$prefixEarlyReturnExpression}} + } + } + + $exception = null; + try { + {{$methodBody}} + } catch (\Exception $e) { + $exception = $e; + } + + if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) { + $returnEarly = false; + $suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $exception ?? $returnValue, $returnEarly); + + if ($returnEarly) { + {{$suffixEarlyReturnExpression}} + } + } + + if ($exception) { + throw $exception; + } + + {{$returnExpression}} + PHP; + + /** + * @param string $methodBody the body of the previously generated code. + * It MUST assign the return value to a variable + * `$returnValue` instead of directly returning + */ + public static function createInterceptedMethodBody( + string $methodBody, + MethodGenerator $method, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors, + ?\ReflectionMethod $originalMethod + ): string { + $replacements = [ + '{{$name}}' => var_export($method->getName(), true), + '{{$prefixInterceptorsName}}' => $prefixInterceptors->getName(), + '{{$prefixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod), + '{{$methodBody}}' => $methodBody, + '{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(), + '{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod), + '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod), + '{{$paramsString}}' => 'array(' . implode(', ', array_map( + static function (ParameterGenerator $parameter): string { + return var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(); + }, + $method->getParameters() + )) + . ')', + ]; + + return str_replace( + array_keys($replacements), + $replacements, + self::TEMPLATE + ); + } +} diff --git a/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php new file mode 100644 index 0000000..700da7e --- /dev/null +++ b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php @@ -0,0 +1,32 @@ +setType('array'); + $this->setParameter($interceptors); + $this->setReturnType('void'); + $this->setBody('$this->' . $prefixInterceptor->getName() . ' = $prefixInterceptors;'); + } +} diff --git a/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php b/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php new file mode 100644 index 0000000..24d5551 --- /dev/null +++ b/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php @@ -0,0 +1,35 @@ +setType('array'); + $this->setParameter($interceptors); + $this->setReturnType('void'); + $this->setBody('$this->' . $suffixInterceptor->getName() . ' = $suffixInterceptors;'); + } +} diff --git a/src/Generator/AccessInterceptorValueHolderGenerator.php b/src/Generator/AccessInterceptorValueHolderGenerator.php deleted file mode 100644 index cd47cac..0000000 --- a/src/Generator/AccessInterceptorValueHolderGenerator.php +++ /dev/null @@ -1,133 +0,0 @@ -isInterface()) { - $interfaces[] = $originalClass->getName(); - } else { - $classGenerator->setExtendedClass($originalClass->getName()); - } - - $classGenerator->setImplementedInterfaces($interfaces); - $classGenerator->addPropertyFromGenerator($valueHolder = new ValueHolderProperty($originalClass)); - $classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors()); - $classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors()); - $classGenerator->addPropertyFromGenerator($publicProperties); - - array_map( - static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void { - ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); - }, - array_merge( - array_map( - $this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors, $valueHolder), - ProxiedMethodsFilter::getProxiedMethods($originalClass) - ), - [ - Constructor::generateMethod($originalClass, $valueHolder), - new StaticProxyConstructor($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors), - new GetWrappedValueHolderValue($valueHolder), - new SetMethodPrefixInterceptor($prefixInterceptors), - new SetMethodSuffixInterceptor($suffixInterceptors), - new MagicGet( - $originalClass, - $valueHolder, - $prefixInterceptors, - $suffixInterceptors, - $publicProperties - ), - new MagicSet( - $originalClass, - $valueHolder, - $prefixInterceptors, - $suffixInterceptors, - $publicProperties - ), - new MagicIsset( - $originalClass, - $valueHolder, - $prefixInterceptors, - $suffixInterceptors, - $publicProperties - ), - new MagicUnset( - $originalClass, - $valueHolder, - $prefixInterceptors, - $suffixInterceptors, - $publicProperties - ), - new MagicClone($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors), - new MagicSleep($originalClass, $valueHolder), - new MagicWakeup($originalClass), - ] - ) - ); - } - - private function buildMethodInterceptor( - MethodPrefixInterceptors $prefixes, - MethodSuffixInterceptors $suffixes, - ValueHolderProperty $valueHolder - ): callable { - return static function (\ReflectionMethod $method) use ($prefixes, $suffixes, $valueHolder): InterceptedMethod { - return InterceptedMethod::generateMethod( - new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), - $valueHolder, - $prefixes, - $suffixes - ); - }; - } -} diff --git a/src/Generator/Factory/AccessInterceptorValueHolderFactory.php b/src/Generator/Factory/AccessInterceptorValueHolderFactory.php deleted file mode 100644 index 3e10a84..0000000 --- a/src/Generator/Factory/AccessInterceptorValueHolderFactory.php +++ /dev/null @@ -1,87 +0,0 @@ -generator = new AccessInterceptorValueHolderGenerator(); - } - - /** - * @param object $instance the object to be wrapped within the value holder - * @param array $prefixInterceptors an array (indexed by method name) of interceptor closures to be called - * before method logic is executed - * @param array $suffixInterceptors an array (indexed by method name) of interceptor closures to be called - * after method logic is executed - * - * @throws InvalidSignatureException - * @throws MissingSignatureException - * @throws \OutOfBoundsException - * - * @psalm-template RealObjectType of object - * - * @psalm-param RealObjectType $instance - * @psalm-param array=, - * RealObjectType=, - * string=, - * array=, - * bool= - * ) : mixed> $prefixInterceptors - * @psalm-param array=, - * RealObjectType=, - * string=, - * array=, - * mixed=, - * bool= - * ) : mixed> $suffixInterceptors - * - * @psalm-return RealObjectType&AccessInterceptorInterface&ValueHolderInterface&AccessInterceptorValueHolderInterface - * - * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not - * interfaced (by design) - */ - public function createProxy( - object $instance, - array $prefixInterceptors = [], - array $suffixInterceptors = [] - ): AccessInterceptorValueHolderInterface { - $proxyClassName = $this->generateProxy(\get_class($instance)); - - /** - * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) - * - * @psalm-suppress MixedMethodCall - * @psalm-suppress MixedReturnStatement - * @noinspection PhpUndefinedMethodInspection - */ - return $proxyClassName::staticProxyConstructor($instance, $prefixInterceptors, $suffixInterceptors); - } - - protected function getGenerator(): ProxyGeneratorInterface - { - return $this->generator; - } -} diff --git a/src/Generator/Method/InterceptorGenerator.php b/src/Generator/Method/InterceptorGenerator.php deleted file mode 100644 index ea06c0a..0000000 --- a/src/Generator/Method/InterceptorGenerator.php +++ /dev/null @@ -1,99 +0,0 @@ -{{$prefixInterceptorsName}}[{{$name}}])) { - $returnEarly = false; - $prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $returnEarly); - - if ($returnEarly) { - {{$returnEarlyPrefixExpression}} - } -} - -$exception = null; -try { - {{$methodBody}} -} catch (\Exception $e) { - $exception = $e; -} - -if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) { - $returnEarly = false; - $suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $exception ?? $returnValue, $returnEarly); - - if ($returnEarly) { - {{$returnEarlySuffixExpression}} - } -} - -if ($exception) { - throw $exception; -} - -{{$returnExpression}} -PHP; - - /** - * @param string $methodBody the body of the previously generated code. - * It MUST assign the return value to a variable - * `$returnValue` instead of directly returning - */ - public static function createInterceptedMethodBody( - string $methodBody, - MethodGenerator $method, - PropertyGenerator $valueHolder, - PropertyGenerator $prefixInterceptors, - PropertyGenerator $suffixInterceptors, - ?\ReflectionMethod $originalMethod - ): string { - $name = var_export($method->getName(), true); - $valueHolderName = $valueHolder->getName(); - $prefixInterceptorsName = $prefixInterceptors->getName(); - $suffixInterceptorsName = $suffixInterceptors->getName(); - $params = []; - - foreach ($method->getParameters() as $parameter) { - $parameterName = $parameter->getName(); - $params[] = var_export($parameterName, true) . ' => $' . $parameter->getName(); - } - - $paramsString = 'array(' . implode(', ', $params) . ')'; - - $replacements = [ - '{{$prefixInterceptorsName}}' => $prefixInterceptorsName, - '{{$name}}' => $name, - '{{$valueHolderName}}' => $valueHolderName, - '{{$paramsString}}' => $paramsString, - '{{$returnEarlyPrefixExpression}}' => ProxiedMethodReturnExpression::generate( - '$prefixReturnValue', - $originalMethod - ), - '{{$methodBody}}' => $methodBody, - '{{$suffixInterceptorsName}}' => $suffixInterceptorsName, - '{{$returnEarlySuffixExpression}}' => ProxiedMethodReturnExpression::generate( - '$suffixReturnValue', - $originalMethod - ), - '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod), - - ]; - - return str_replace(array_keys($replacements), $replacements, self::TEMPLATE); - } -} diff --git a/src/Model/Request/Instance.php b/src/Model/Request/Instance.php index 8a44678..c0ee782 100644 --- a/src/Model/Request/Instance.php +++ b/src/Model/Request/Instance.php @@ -11,9 +11,7 @@ final class Instance { private Method $method; - private object $object; - - private \ReflectionObject $reflection; + private \ReflectionClass $reflection; private function __construct() { @@ -26,13 +24,13 @@ private function __construct() * @throws AnnotationException */ public static function createFromMethod( - object $object, + string $class, string $methodName, ?array $parameters = null, mixed $response = null ): self { $annotationReader = new AnnotationReader(); - $reflection = new \ReflectionObject($object); + $reflection = new \ReflectionClass($class); $methodRef = $reflection->getMethod($methodName); $annotations = $annotationReader->getMethodAnnotations($methodRef); $method = Method::create($methodRef, $annotations); @@ -43,7 +41,7 @@ public static function createFromMethod( $method->setResponse($response); } - return self::create($object, $reflection, $method); + return self::create($reflection, $method); } public function getMethod(): Method @@ -52,12 +50,10 @@ public function getMethod(): Method } public static function create( - object $object, - \ReflectionObject $reflection, + \ReflectionClass $reflection, Method $method ): self { $self = new self(); - $self->object = $object; $self->reflection = $reflection; $self->method = $method; @@ -81,12 +77,7 @@ public function setResponse(mixed $response): self return $this; } - public function getObject(): object - { - return $this->object; - } - - public function getReflection(): \ReflectionObject + public function getReflection(): \ReflectionClass { return $this->reflection; } diff --git a/src/Proxy/AccessInterceptorsInterface.php b/src/Proxy/AccessInterceptorsInterface.php new file mode 100644 index 0000000..964422c --- /dev/null +++ b/src/Proxy/AccessInterceptorsInterface.php @@ -0,0 +1,15 @@ + $class + * @param mixed ...$agrs * * @return T */ - public function createProxy(object $object): object + public function createInstance(string $class, ...$args): object { - $instanceRef = new \ReflectionObject($object); - $methods = $instanceRef->getMethods(\ReflectionMethod::IS_PUBLIC); + $instanceRef = new \ReflectionClass($class); + + if ($instanceRef->isFinal()) { + throw new \LogicException(sprintf("Unable to proxify final class %s. Hint: replace final keywords with a @final annotation", $instanceRef->getName())); + } + + $methods = $instanceRef->getMethods(); $interceptionClosures = []; foreach ($methods as $methodRef) { $methodAnnotations = $this->annotationReader->getMethodAnnotations($methodRef); + + if (count($methodAnnotations) === 0 && count($methodRef->getAttributes()) === 0) { + continue; + } + $instance = Instance::create( - $object, $instanceRef, Method::create( $methodRef, @@ -80,6 +92,14 @@ public function createProxy(object $object): object foreach ([PrefixInterceptor::PREFIX_TYPE, SuffixInterceptor::SUFFIX_TYPE] as $type) { $interceptors = $this->filterInterceptors($instance, $type); if (\count($interceptors) > 0) { + if ($methodRef->isFinal()) { + throw new \LogicException(sprintf("Unable to proxify a final method %s on class %s. Hint: replace final keywords with a @final annotation", $methodRef->getName(), $methodRef->getDeclaringClass())); + } + + if ($methodRef->isPrivate()) { + throw new \LogicException(sprintf("Unable to attach an interceptor to a private method %s on class %s.", $methodRef->getName(), $methodRef->getDeclaringClass())); + } + $interceptionClosures[$type][$methodRef->getName()] = $this->getInterceptionClosure( $type, $interceptors, @@ -90,12 +110,13 @@ public function createProxy(object $object): object } if (\count($interceptionClosures) === 0) { - return $object; + return new $class(...$args); } return $this->getInterceptorFactory() - ->createProxy( - $object, + ->createInstance( + $class, + $args, $interceptionClosures[PrefixInterceptor::PREFIX_TYPE] ?? [], $interceptionClosures[SuffixInterceptor::SUFFIX_TYPE] ?? [], ) @@ -252,10 +273,10 @@ private function getInterceptionClosure( }; } - private function getInterceptorFactory(): AccessInterceptorValueHolderFactory + private function getInterceptorFactory(): AccessInterceptorFactory { if ($this->configuration->isEval()) { - return new AccessInterceptorValueHolderFactory(); + return new AccessInterceptorFactory(); } $proxiesDir = $this->configuration->getProxiesDir(); @@ -270,6 +291,6 @@ private function getInterceptorFactory(): AccessInterceptorValueHolderFactory // @phpstan-ignore-next-line spl_autoload_register($conf->getProxyAutoloader()); - return new AccessInterceptorValueHolderFactory($conf); + return new AccessInterceptorFactory($conf); } } diff --git a/tests/Double/Stub/Cache/ClassWithCacheAttributes.php b/tests/Double/Stub/Cache/ClassWithCacheAttributes.php index ab4db92..e5461e4 100644 --- a/tests/Double/Stub/Cache/ClassWithCacheAttributes.php +++ b/tests/Double/Stub/Cache/ClassWithCacheAttributes.php @@ -11,6 +11,13 @@ class ClassWithCacheAttributes { public const DATA = 'data'; + public readonly string $data; + + public function __construct(?string $data = null) + { + $this->data = $data ?? 'hello'; + } + public function methodWithoutAttribute(): bool { return true; diff --git a/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php b/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php index 66017f7..b3ff466 100644 --- a/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php +++ b/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php @@ -11,6 +11,8 @@ class ClassWithInvalidateCacheAttributes { public const DATA = 'data'; + private $data; + #[Cache(tags: ['"my_tag"'])] public function methodWithTaggedCache(): string { diff --git a/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php b/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php new file mode 100644 index 0000000..895aba3 --- /dev/null +++ b/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php @@ -0,0 +1,14 @@ +expectException(HandlerNotFound::class); $this->getProxyFactory( [new CacheInterceptor($config, [new CacheHandlerMock()])] - )->createProxy(new ClassWithCacheAttributes()) + )->createInstance(ClassWithCacheAttributes::class) ->invalidHandler(); } diff --git a/tests/Interceptor/CacheInterceptorTest.php b/tests/Interceptor/CacheInterceptorTest.php index 2732403..3717889 100644 --- a/tests/Interceptor/CacheInterceptorTest.php +++ b/tests/Interceptor/CacheInterceptorTest.php @@ -53,7 +53,7 @@ protected function tearDown(): void public function testSupportsCacheAttribute(): void { $method = Instance::createFromMethod( - new ClassWithCacheAttributes(), + ClassWithCacheAttributes::class, 'methodWithoutAttribute' ); @@ -61,7 +61,7 @@ public function testSupportsCacheAttribute(): void $this->assertFalse($this->cacheInterceptor->supportsSuffix($method)); $method = Instance::createFromMethod( - new ClassWithCacheAttributes(), + ClassWithCacheAttributes::class, 'methodWithAttribute' ); @@ -72,7 +72,7 @@ public function testSupportsCacheAttribute(): void public function testNotSupportsCacheAnnotation(): void { $method = Instance::createFromMethod( - new LegacyCacheAnnotatedClass(), + LegacyCacheAnnotatedClass::class, 'annotatedMethod' ); @@ -82,14 +82,14 @@ public function testNotSupportsCacheAnnotation(): void public function testMethodWithoutCache(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $this->assertEquals(ClassWithCacheAttributes::DATA, $proxy->methodWithAttribute()); } public function testNotInCacheReturnData(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $this->assertEquals(ClassWithCacheAttributes::DATA, $proxy->methodWithAttribute()); $this->assertEmpty($this->cacheInterceptor->getHits()); @@ -98,7 +98,7 @@ public function testNotInCacheReturnData(): void public function testInCacheReturnData(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithAttribute(); $this->assertEmpty($this->cacheInterceptor::getHits()); @@ -113,7 +113,7 @@ public function testInCacheReturnData(): void public function testMethodWithVoidReturnIsNotCached(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithVoidReturn(); $this->assertEmpty($this->cacheInterceptor->getHits()); @@ -128,7 +128,7 @@ public function testMethodWithVoidReturnIsNotCached(): void public function testCachedMethodWithArguments(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithArguments('value1', 'value2'); $this->assertEmpty($this->cacheInterceptor->getHits()); @@ -152,7 +152,7 @@ public function testCachedMethodWithArguments(): void public function testOnExceptionDontSave(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); try { $proxy->methodWithException(); @@ -173,7 +173,7 @@ public function testOnExceptionDontSave(): void public function testWithLifeTimeReturnData(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $data = $proxy->methodWithLifetime(); @@ -183,7 +183,7 @@ public function testWithLifeTimeReturnData(): void public function testWithTagsReturnDataAndCanBeInvalidated(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithTaggedCache(); $this->assertEmpty($this->cacheInterceptor->getHits()); @@ -206,7 +206,7 @@ public function testWithTagsReturnDataAndCanBeInvalidated(): void public function testWithTagsAndParameterReturnDataAndCanBeInvalidated(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithResolvedTag(new ParameterClassStub()); $this->assertEmpty($this->cacheInterceptor->getHits()); @@ -227,7 +227,7 @@ public function testWithTagsAndParameterReturnDataAndCanBeInvalidated(): void public function testMethodCacheIsAutoTaggedFromResponse(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithAttributeReturningObject(); $this->assertEmpty($this->cacheInterceptor::getHits()); @@ -251,7 +251,7 @@ public function testMethodCacheIsAutoTaggedFromResponse(): void public function testMethodWithPhpDoc(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithAttributeAndPhpDoc(); $this->assertEmpty($this->cacheInterceptor::getHits()); @@ -266,35 +266,42 @@ public function testMethodWithPhpDoc(): void public function testUnknownHandlerThrowsException(): void { $this->expectException(HandlerNotFound::class); - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->invalidHandler(); } public function testUnknownPoolThrowsException(): void { $this->expectException(\InvalidArgumentException::class); - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->invalidPool(); } public function testPool(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $this->assertEquals(ClassWithCacheAttributes::DATA, $proxy->methodWithPool()); $this->assertEmpty($this->cacheInterceptor::getHits()); $this->assertNotEmpty($this->cacheInterceptor::getMisses()); } + public function testProperties(): void + { + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class, 'world'); + + $this->assertEquals('world', $proxy->data); + } + public function testBothHandlerAndPool(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $this->assertEquals(ClassWithCacheAttributes::DATA, $proxy->bothHandlerAndPool()); } public function testMethodWithMultiplePools(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithMultiplePools(); $this->assertEmpty($this->cacheInterceptor->getHits('foo')); @@ -316,7 +323,7 @@ public function testMethodWithMultiplePools(): void public function testMethodWithNoPoolUsesDefaultPool(): void { - $proxy = $this->proxyFactory->createProxy(new ClassWithCacheAttributes()); + $proxy = $this->proxyFactory->createInstance(ClassWithCacheAttributes::class); $proxy->methodWithAttribute(); $this->assertNotEmpty($this->cacheInterceptor->getMisses('default')); diff --git a/tests/Interceptor/EventInterceptorTest.php b/tests/Interceptor/EventInterceptorTest.php index 191c70a..ebc2481 100644 --- a/tests/Interceptor/EventInterceptorTest.php +++ b/tests/Interceptor/EventInterceptorTest.php @@ -41,13 +41,13 @@ protected function setUp(): void ), ] ); - $this->proxy = $this->proxyFactory->createProxy(new EventAnnotatedClass()); + $this->proxy = $this->proxyFactory->createInstance(EventAnnotatedClass::class); } public function testInvalidMethodEventThrowException(): void { $this->expectException(\InvalidArgumentException::class); - $proxy = $this->proxyFactory->createProxy(new InvalidMethodEventAnnotatedClass()); + $proxy = $this->proxyFactory->createInstance(InvalidMethodEventAnnotatedClass::class); $proxy->eventWithWrongMethods(); } diff --git a/tests/Interceptor/InvalidateCacheInterceptorTest.php b/tests/Interceptor/InvalidateCacheInterceptorTest.php index 58d0d3f..9c97280 100644 --- a/tests/Interceptor/InvalidateCacheInterceptorTest.php +++ b/tests/Interceptor/InvalidateCacheInterceptorTest.php @@ -42,7 +42,7 @@ protected function setUp(): void $this->invalidateCacheInterceptor, ]); - $this->proxy = $this->proxyFactory->createProxy(new ClassWithInvalidateCacheAttributes()); + $this->proxy = $this->proxyFactory->createInstance(ClassWithInvalidateCacheAttributes::class); } protected function tearDown(): void diff --git a/tests/Interceptor/LegacyCacheInterceptorTest.php b/tests/Interceptor/LegacyCacheInterceptorTest.php index ea78cbf..eb315c5 100644 --- a/tests/Interceptor/LegacyCacheInterceptorTest.php +++ b/tests/Interceptor/LegacyCacheInterceptorTest.php @@ -34,13 +34,13 @@ protected function setUp(): void $this->proxyFactory = $this->getProxyFactory([ $this->cacheInterceptor, ]); - $this->proxy = $this->proxyFactory->createProxy(new CacheAnnotatedClass()); + $this->proxy = $this->proxyFactory->createInstance(CacheAnnotatedClass::class); } public function testTooLongIdWithIdThrowException(): void { $this->expectException(AnnotationException::class); - $this->proxyFactory->createProxy(new InvalidIdCacheAnnotatedClass()); + $this->proxyFactory->createInstance(InvalidIdCacheAnnotatedClass::class); } public function testTagsInvalidationThrowException(): void diff --git a/tests/Interceptor/SecurityInterceptorTest.php b/tests/Interceptor/SecurityInterceptorTest.php index b2af623..013e8a0 100644 --- a/tests/Interceptor/SecurityInterceptorTest.php +++ b/tests/Interceptor/SecurityInterceptorTest.php @@ -33,7 +33,7 @@ protected function setUp(): void ), ] ); - $this->proxy = $this->proxyFactory->createProxy(new SecurityAnnotatedClass()); + $this->proxy = $this->proxyFactory->createInstance(SecurityAnnotatedClass::class); } public function testOnlyRoleNotAuthorizedThrowException(): void diff --git a/tests/Interceptor/TestCodeTemplate.php b/tests/Interceptor/TestCodeTemplate.php index 921159f..0e766c8 100644 --- a/tests/Interceptor/TestCodeTemplate.php +++ b/tests/Interceptor/TestCodeTemplate.php @@ -33,11 +33,10 @@ ); $classname = '\\OpenClassrooms\\ServiceProxy\\Tests\\tmp\\' . $argv[1]; -$instance = new $classname(); -$proxy = $proxyFactory->createProxy($instance); +$instance = $proxyFactory->createInstance($classname); -$proxy->execute(); +$instance->execute(); $cacheHit = !empty($cacheInterceptor->getHits()); diff --git a/tests/ProxyFactoryTest.php b/tests/ProxyFactoryTest.php index 9b002c7..f21984b 100644 --- a/tests/ProxyFactoryTest.php +++ b/tests/ProxyFactoryTest.php @@ -17,6 +17,9 @@ use OpenClassrooms\ServiceProxy\Interceptor\Impl\TransactionInterceptor; use OpenClassrooms\ServiceProxy\ProxyFactory; use OpenClassrooms\ServiceProxy\Tests\Double\Stub\Cache\ClassWithCacheAttributes; +use OpenClassrooms\ServiceProxy\Tests\Double\Stub\ClassWithAnnotationOnPrivateMethod; +use OpenClassrooms\ServiceProxy\Tests\Double\Stub\ClassWithFinalMethod; +use OpenClassrooms\ServiceProxy\Tests\Double\Stub\FinalClass; use OpenClassrooms\ServiceProxy\Tests\Double\Stub\WithConstructorAnnotationClass; use OpenClassrooms\ServiceProxy\Tests\Double\Stub\WithoutAnnotationClass; use PHPUnit\Framework\TestCase; @@ -50,33 +53,49 @@ protected function setUp(): void $this->factory = $this->getProxyFactory($interceptors); } - public function testWithoutAnnotationReturnServiceProxyInterface(): void + public function testWithoutAnnotationReturnUnmodifiedObject(): void { - $inputClass = new WithoutAnnotationClass(); - $inputClass->field = true; - $proxy = $this->factory->createProxy($inputClass); + $instance = $this->factory->createInstance(WithoutAnnotationClass::class); + $instance->field = true; - $this->assertTrue($proxy->aMethodWithoutAnnotation()); - $this->assertTrue($proxy->aMethodWithoutServiceProxyAnnotation()); - $this->assertNotProxy($inputClass, $proxy); + $this->assertTrue($instance->aMethodWithoutAnnotation()); + $this->assertTrue($instance->aMethodWithoutServiceProxyAnnotation()); + $this->assertNotProxy(WithoutAnnotationClass::class, $instance); + } + + + public function testThrownExceptionWhenCreateAProxyOnFinalClass(): void + { + $this->expectException(\LogicException::class); + $this->factory->createInstance(FinalClass::class); + } + + public function testThrownExceptionOnFinalMethodInterception(): void + { + $this->expectException(\LogicException::class); + $this->factory->createInstance(ClassWithFinalMethod::class); + } + + public function testThrownExceptionOnPrivateMethodInterception(): void + { + $this->expectException(\LogicException::class); + $this->factory->createInstance(ClassWithAnnotationOnPrivateMethod::class); } public function testWithCacheAnnotationReturnServiceProxyCacheInterface(): void { - $inputClass = new ClassWithCacheAttributes(); - $proxy = $this->factory->createProxy($inputClass); + $instance = $this->factory->createInstance(ClassWithCacheAttributes::class); - $this->assertProxy($inputClass, $proxy); - $this->assertTrue($proxy->methodWithoutAttribute()); + $this->assertProxy(ClassWithCacheAttributes::class, $instance); + $this->assertTrue($instance->methodWithoutAttribute()); } public function testWithCacheAnnotationWithConstructorReturnServiceProxyCacheInterface(): void { - $inputClass = new WithConstructorAnnotationClass('test'); - $proxy = $this->factory->createProxy($inputClass); + $instance = $this->factory->createInstance(WithConstructorAnnotationClass::class, 'test'); - $this->assertProxy($inputClass, $proxy); - $this->assertTrue($proxy->aMethodWithoutAnnotation()); + $this->assertProxy(WithConstructorAnnotationClass::class, $instance); + $this->assertTrue($instance->aMethodWithoutAnnotation()); } public function testCheckInterceptorsOrders(): void diff --git a/tests/ProxyTestTrait.php b/tests/ProxyTestTrait.php index bfdabe7..00292e1 100644 --- a/tests/ProxyTestTrait.php +++ b/tests/ProxyTestTrait.php @@ -6,6 +6,7 @@ use OpenClassrooms\ServiceProxy\Interceptor\Contract\PrefixInterceptor; use OpenClassrooms\ServiceProxy\Interceptor\Contract\SuffixInterceptor; +use OpenClassrooms\ServiceProxy\Proxy\AccessInterceptorsInterface; use OpenClassrooms\ServiceProxy\ProxyFactory; use OpenClassrooms\ServiceProxy\ProxyFactoryConfiguration; use PHPUnit\Framework\TestCase as Assert; @@ -23,18 +24,22 @@ protected function tearDown(): void $fs->remove(self::$cacheDir); } - protected function assertProxy(object $input, object $proxy): void + /** + * @param class-string $input + */ + protected function assertProxy(string $input, object $proxy): void { - Assert::assertInstanceOf(\get_class($input), $proxy); - Assert::assertInstanceOf(ValueHolderInterface::class, $proxy); - Assert::assertInstanceOf(AccessInterceptorInterface::class, $proxy); + Assert::assertInstanceOf($input, $proxy); + Assert::assertInstanceOf(AccessInterceptorsInterface::class, $proxy); } - protected function assertNotProxy(object $input, object $proxy): void + /** + * @param class-string $class + */ + protected function assertNotProxy(string $class, object $proxy): void { - Assert::assertInstanceOf(\get_class($input), $proxy); - Assert::assertNotInstanceOf(ValueHolderInterface::class, $proxy); - Assert::assertNotInstanceOf(AccessInterceptorInterface::class, $proxy); + Assert::assertInstanceOf($class, $proxy); + Assert::assertNotInstanceOf(AccessInterceptorsInterface::class, $proxy); } /** From c81fca08480cacb20ca262b8a91f5dba37477ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Mon, 25 Mar 2024 11:40:59 +0100 Subject: [PATCH 02/15] Fix typing and code style --- .../Symfony/CacheWarmer/ProxyCacheWarmer.php | 15 ++++++-- .../Symfony/Config/services.php | 8 +++-- .../Compiler/ServiceProxyPass.php | 1 - .../Subscriber/ServiceProxySubscriber.php | 15 ++------ .../AccessInterceptorGenerator.php | 20 +++++++---- .../Factory/AccessInterceptorFactory.php | 14 +++++--- .../Method/InterceptedMethod.php | 3 +- .../Method/InterceptorGenerator.php | 36 ++++++++++++------- .../Method/SetMethodPrefixInterceptors.php | 4 ++- .../Method/SetMethodSuffixInterceptors.php | 1 - src/Handler/Contract/EventHandler.php | 5 +++ .../SymfonyEventDispatcherEventHandler.php | 7 ++++ .../Contract/PrefixInterceptor.php | 10 ++++++ .../Contract/StartUpInterceptor.php | 10 ++++++ .../Contract/SuffixInterceptor.php | 10 ++++++ src/Interceptor/Impl/CacheInterceptor.php | 9 +++++ .../Impl/InvalidateCacheInterceptor.php | 4 +++ .../Impl/LegacyCacheInterceptor.php | 19 ++++++++++ src/Interceptor/Impl/ListenInterceptor.php | 10 ++++++ src/Interceptor/Impl/SecurityInterceptor.php | 5 +++ .../Impl/TransactionInterceptor.php | 4 +++ src/Invoker/Contract/MethodInvoker.php | 4 +++ src/Invoker/Impl/AggregateMethodInvoker.php | 7 +++- src/Model/Event.php | 5 +++ src/Model/Request/Instance.php | 23 ++++++++++++ src/Proxy/AccessInterceptorsInterface.php | 18 +++++++--- src/ProxyFactory.php | 32 +++++++++++++---- .../ClassWithAnnotationOnPrivateMethod.php | 3 +- tests/Double/Stub/ClassWithFinalMethod.php | 7 ++-- tests/Double/Stub/FinalClass.php | 5 +-- tests/ProxyFactoryTest.php | 1 - tests/ProxyTestTrait.php | 2 -- 32 files changed, 248 insertions(+), 69 deletions(-) diff --git a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php index f099d21..7d106ab 100644 --- a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php +++ b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php @@ -1,16 +1,23 @@ + */ private iterable $proxies; + /** + * @param iterable $proxies + */ public function __construct(iterable $proxies) { $this->proxies = $proxies; @@ -19,7 +26,7 @@ public function __construct(iterable $proxies) /** * {@inheritdoc} */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir): array { foreach ($this->proxies as $proxy) { if ($proxy instanceof LazyLoadingInterface && !$proxy->isProxyInitialized()) { @@ -32,6 +39,8 @@ public function warmUp(string $cacheDir) $proxy->initializeLazyObject(); } } + + return []; } /** diff --git a/src/FrameworkBridge/Symfony/Config/services.php b/src/FrameworkBridge/Symfony/Config/services.php index c910c39..3ad9724 100644 --- a/src/FrameworkBridge/Symfony/Config/services.php +++ b/src/FrameworkBridge/Symfony/Config/services.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\CacheWarmer\ProxyCacheWarmer; use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\Messenger\Transport\Serialization\MessageSerializer; use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\Subscriber\ServiceProxySubscriber; use OpenClassrooms\ServiceProxy\Handler\Impl\Cache\SymfonyCacheHandler; @@ -10,7 +11,6 @@ use OpenClassrooms\ServiceProxy\ProxyFactory; use OpenClassrooms\ServiceProxy\ProxyFactoryConfiguration; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\CacheWarmer\ProxyCacheWarmer; use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service; use function Symfony\Component\DependencyInjection\Loader\Configurator\param; use function Symfony\Component\DependencyInjection\Loader\Configurator\service; @@ -79,9 +79,11 @@ $services->set(ProxyCacheWarmer::class) ->args([ - tagged_iterator('openclassrooms.service_proxy') + tagged_iterator('openclassrooms.service_proxy'), ]) - ->tag('kernel.cache_warmer', ['priority' => 128]); + ->tag('kernel.cache_warmer', [ + 'priority' => 128, + ]); $services->set(ServiceProxyEventFactory::class) ->autowire() diff --git a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php index ad7bd3b..b45a97d 100644 --- a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php +++ b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php @@ -8,7 +8,6 @@ use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; final class ServiceProxyPass implements CompilerPassInterface diff --git a/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php b/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php index 1129550..59112d1 100644 --- a/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php +++ b/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php @@ -9,7 +9,6 @@ use OpenClassrooms\ServiceProxy\Interceptor\Contract\StartUpInterceptor; use OpenClassrooms\ServiceProxy\Model\Request\Instance; use OpenClassrooms\ServiceProxy\Model\Request\Method; -use ProxyManager\Proxy\ValueHolderInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -73,24 +72,16 @@ public function startUp(RequestEvent $event): void } /** - * @return iterable + * @return iterable> */ public function getInstances(): iterable { foreach ($this->proxies as $proxy) { - $object = $proxy; - if ($proxy instanceof ValueHolderInterface) { - $object = $proxy->getWrappedValueHolderValue(); - if ($object === null) { - continue; - } - } - $instanceRef = new \ReflectionObject($object); - $methods = $instanceRef->getMethods(\ReflectionMethod::IS_PUBLIC); + $instanceRef = new \ReflectionClass($proxy); + $methods = $instanceRef->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED); foreach ($methods as $methodRef) { $methodAnnotations = $this->annotationReader->getMethodAnnotations($methodRef); $instance = Instance::create( - $proxy, $instanceRef, Method::create( $methodRef, diff --git a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php index de34a3d..18b579f 100644 --- a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php +++ b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php @@ -1,13 +1,15 @@ } $proxyOptions * - * @return void - * * @throws \InvalidArgumentException * @throws InvalidProxiedClassException */ public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) { - if (!array_key_exists('methods', $proxyOptions)) { - throw new \InvalidArgumentException(sprintf("Generator %s needs a methods proxyOptions",__CLASS__)); + if (!\array_key_exists('methods', $proxyOptions)) { + throw new \InvalidArgumentException(sprintf('Generator %s needs a methods proxyOptions', __CLASS__)); } CanProxyAssertion::assertClassCanBeProxied($originalClass, false); @@ -48,7 +48,10 @@ static function (MethodGenerator $generatedMethod) use ($originalClass, $classGe array_merge( array_map( $this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors), - array_map(static fn (string $method) => $originalClass->getMethod($method), $proxyOptions['methods']) + array_map( + static fn (string $method) => $originalClass->getMethod($method), + $proxyOptions['methods'] + ) ), [ new SetMethodPrefixInterceptors($prefixInterceptors), @@ -62,7 +65,10 @@ private function buildMethodInterceptor( MethodPrefixInterceptors $prefixInterceptors, MethodSuffixInterceptors $suffixInterceptors ): callable { - return static function (\ReflectionMethod $method) use ($prefixInterceptors, $suffixInterceptors): InterceptedMethod { + return static function (\ReflectionMethod $method) use ( + $prefixInterceptors, + $suffixInterceptors + ): InterceptedMethod { return InterceptedMethod::generateMethod( new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), $prefixInterceptors, diff --git a/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php b/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php index 882d913..4efbbf3 100644 --- a/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php +++ b/src/Generator/AccessInterceptorGenerator/Factory/AccessInterceptorFactory.php @@ -1,4 +1,6 @@ - $class * @param array $args * @param array $prefixInterceptors an array (indexed by method name) of interceptor closures to be called @@ -41,13 +43,15 @@ public function createInstance( ); $methods = array_unique($methods); - $proxyClassName = $this->generateProxy($class, ['methods' => $methods]); + $proxyClassName = $this->generateProxy($class, [ + 'methods' => $methods, + ]); $instance = new $proxyClassName(...$args); - + $instance->setPrefixInterceptors($prefixInterceptors); $instance->setSuffixInterceptors($suffixInterceptors); - + return $instance; } diff --git a/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php b/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php index 11772cf..a07f13b 100644 --- a/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php +++ b/src/Generator/AccessInterceptorGenerator/Method/InterceptedMethod.php @@ -8,7 +8,6 @@ use Laminas\Code\Generator\PropertyGenerator; use Laminas\Code\Reflection\MethodReflection; -use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method\InterceptorGenerator; use ProxyManager\Generator\MethodGenerator; /** @@ -24,7 +23,7 @@ public static function generateMethod( PropertyGenerator $prefixInterceptors, PropertyGenerator $suffixInterceptors ): self { - $method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod); + $method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod); $forwardedParams = []; foreach ($originalMethod->getParameters() as $parameter) { diff --git a/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php index af08101..a5aadd8 100644 --- a/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php +++ b/src/Generator/AccessInterceptorGenerator/Method/InterceptorGenerator.php @@ -63,19 +63,29 @@ public static function createInterceptedMethodBody( ?\ReflectionMethod $originalMethod ): string { $replacements = [ - '{{$name}}' => var_export($method->getName(), true), - '{{$prefixInterceptorsName}}' => $prefixInterceptors->getName(), - '{{$prefixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod), - '{{$methodBody}}' => $methodBody, - '{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(), - '{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod), - '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod), - '{{$paramsString}}' => 'array(' . implode(', ', array_map( - static function (ParameterGenerator $parameter): string { - return var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(); - }, - $method->getParameters() - )) + '{{$name}}' => var_export($method->getName(), true), + '{{$prefixInterceptorsName}}' => $prefixInterceptors->getName(), + '{{$prefixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate( + '$prefixReturnValue', + $originalMethod + ), + '{{$methodBody}}' => $methodBody, + '{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(), + '{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate( + '$suffixReturnValue', + $originalMethod + ), + '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate( + '$returnValue', + $originalMethod + ), + '{{$paramsString}}' => 'array(' . implode(', ', array_map( + static fn (ParameterGenerator $parameter): string => var_export( + $parameter->getName(), + true + ) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(), + $method->getParameters() + )) . ')', ]; diff --git a/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php index 700da7e..3fd2538 100644 --- a/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php +++ b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php @@ -1,4 +1,6 @@ - $instance + */ public function listen(Instance $instance, string $name, Transport $transport = null, int $priority = 0): void; } diff --git a/src/Handler/Impl/Event/SymfonyEventDispatcherEventHandler.php b/src/Handler/Impl/Event/SymfonyEventDispatcherEventHandler.php index a92367c..68b467f 100644 --- a/src/Handler/Impl/Event/SymfonyEventDispatcherEventHandler.php +++ b/src/Handler/Impl/Event/SymfonyEventDispatcherEventHandler.php @@ -50,6 +50,13 @@ public function listen(Instance $instance, string $name, ?Transport $transport = ); } + /** + * @template T of object + * + * @param Instance $instance + * + * @return callable(object): mixed + */ private function getCallable(Instance $instance): callable { return fn (object $event) => $this->aggregateMethodInvoker->invoke($instance, $event); diff --git a/src/Interceptor/Contract/PrefixInterceptor.php b/src/Interceptor/Contract/PrefixInterceptor.php index 593e79a..574a7c7 100644 --- a/src/Interceptor/Contract/PrefixInterceptor.php +++ b/src/Interceptor/Contract/PrefixInterceptor.php @@ -11,8 +11,18 @@ interface PrefixInterceptor { public const PREFIX_TYPE = 'prefix'; + /** + * @template T of object + * + * @param Instance $instance + */ public function prefix(Instance $instance): Response; + /** + * @template T of object + * + * @param Instance $instance + */ public function supportsPrefix(Instance $instance): bool; public function getPrefixPriority(): int; diff --git a/src/Interceptor/Contract/StartUpInterceptor.php b/src/Interceptor/Contract/StartUpInterceptor.php index 6cc0320..ca30c1b 100644 --- a/src/Interceptor/Contract/StartUpInterceptor.php +++ b/src/Interceptor/Contract/StartUpInterceptor.php @@ -11,8 +11,18 @@ interface StartUpInterceptor { public const PREFIX_TYPE = 'startUp'; + /** + * @template T of object + * + * @param Instance $instance + */ public function startUp(Instance $instance): Response; + /** + * @template T of object + * + * @param Instance $instance + */ public function supportsStartUp(Instance $instance): bool; public function getStartUpPriority(): int; diff --git a/src/Interceptor/Contract/SuffixInterceptor.php b/src/Interceptor/Contract/SuffixInterceptor.php index 33db462..02fac02 100644 --- a/src/Interceptor/Contract/SuffixInterceptor.php +++ b/src/Interceptor/Contract/SuffixInterceptor.php @@ -11,8 +11,18 @@ interface SuffixInterceptor { public const SUFFIX_TYPE = 'suffix'; + /** + * @template T of object + * + * @param Instance $instance + */ public function suffix(Instance $instance): Response; + /** + * @template T of object + * + * @param Instance $instance + */ public function supportsSuffix(Instance $instance): bool; public function getSuffixPriority(): int; diff --git a/src/Interceptor/Impl/CacheInterceptor.php b/src/Interceptor/Impl/CacheInterceptor.php index 884b6c4..fb432cf 100644 --- a/src/Interceptor/Impl/CacheInterceptor.php +++ b/src/Interceptor/Impl/CacheInterceptor.php @@ -181,6 +181,11 @@ public function getSuffixPriority(): int return 20; } + /** + * @template T of object + * + * @param Instance $instance + */ private function buildCacheKey(Instance $instance, Cache $attribute): string { $identifier = implode( @@ -281,6 +286,10 @@ private function getInnerCode(\ReflectionMethod|\ReflectionClass $reflection): s } /** + * @template T of object + * + * @param Instance $instance + * * @return array */ private function getTags(Instance $instance, Cache $attribute, mixed $response = null): array diff --git a/src/Interceptor/Impl/InvalidateCacheInterceptor.php b/src/Interceptor/Impl/InvalidateCacheInterceptor.php index 7f3565b..3c1a2e1 100644 --- a/src/Interceptor/Impl/InvalidateCacheInterceptor.php +++ b/src/Interceptor/Impl/InvalidateCacheInterceptor.php @@ -49,6 +49,10 @@ public function getSuffixPriority(): int } /** + * @template T of object + * + * @param Instance $instance + * * @return array */ private function getTags(Instance $instance, InvalidateCache $attribute): array diff --git a/src/Interceptor/Impl/LegacyCacheInterceptor.php b/src/Interceptor/Impl/LegacyCacheInterceptor.php index 5227ef4..ed4ae99 100644 --- a/src/Interceptor/Impl/LegacyCacheInterceptor.php +++ b/src/Interceptor/Impl/LegacyCacheInterceptor.php @@ -18,6 +18,11 @@ */ final class LegacyCacheInterceptor extends AbstractInterceptor implements SuffixInterceptor, PrefixInterceptor { + /** + * @template T of object + * + * @param Instance $instance + */ public function prefix(Instance $instance): Response { $annotation = $instance->getMethod() @@ -98,6 +103,11 @@ public function getSuffixPriority(): int return 20; } + /** + * @template T of object + * + * @param Instance $instance + */ private function getNamespace(Instance $instance, Cache $annotation): ?string { $parameters = $instance->getMethod() @@ -115,6 +125,11 @@ private function getNamespace(Instance $instance, Cache $annotation): ?string return null; } + /** + * @template T of object + * + * @param Instance $instance + */ private function getProxyId(Instance $instance, Cache $annotation): string { $parameters = $instance->getMethod() @@ -139,6 +154,10 @@ private function getProxyId(Instance $instance, Cache $annotation): string } /** + * @template T of object + * + * @param Instance $instance + * * @return array */ private function getTags(Instance $instance, Cache $annotation): array diff --git a/src/Interceptor/Impl/ListenInterceptor.php b/src/Interceptor/Impl/ListenInterceptor.php index 0129557..88e72a2 100644 --- a/src/Interceptor/Impl/ListenInterceptor.php +++ b/src/Interceptor/Impl/ListenInterceptor.php @@ -14,6 +14,11 @@ final class ListenInterceptor extends AbstractInterceptor implements StartUpInterceptor { + /** + * @template T of object + * + * @param Instance $instance + */ public function startUp(Instance $instance): Response { $attributes = $instance->getMethod() @@ -37,6 +42,11 @@ public function startUp(Instance $instance): Response return new Response(); } + /** + * @template T of object + * + * @param Instance $instance + */ public function supportsStartUp(Instance $instance): bool { return $instance->getMethod() diff --git a/src/Interceptor/Impl/SecurityInterceptor.php b/src/Interceptor/Impl/SecurityInterceptor.php index a7431a5..37994e2 100644 --- a/src/Interceptor/Impl/SecurityInterceptor.php +++ b/src/Interceptor/Impl/SecurityInterceptor.php @@ -91,6 +91,11 @@ public function supportsPrefix(Instance $instance): bool ; } + /** + * @template T of object + * + * @param Instance $instance + */ private function guessRoleName(Instance $instance): string { $className = $instance->getReflection() diff --git a/src/Interceptor/Impl/TransactionInterceptor.php b/src/Interceptor/Impl/TransactionInterceptor.php index 726392f..5c3c208 100644 --- a/src/Interceptor/Impl/TransactionInterceptor.php +++ b/src/Interceptor/Impl/TransactionInterceptor.php @@ -81,6 +81,10 @@ public function getSuffixPriority(): int } /** + * @template T of object + * + * @param Instance $instance + * * @throws \Exception */ private function handleMappedException(\Exception $thrownException, Transaction $attribute): void diff --git a/src/Invoker/Contract/MethodInvoker.php b/src/Invoker/Contract/MethodInvoker.php index f42f507..c80b17d 100644 --- a/src/Invoker/Contract/MethodInvoker.php +++ b/src/Invoker/Contract/MethodInvoker.php @@ -9,6 +9,10 @@ interface MethodInvoker { /** + * @template T of object + * + * @param Instance $listenerInstance + * * @throws \InvalidArgumentException */ public function invoke(Instance $listenerInstance, ?object $event = null): mixed; diff --git a/src/Invoker/Impl/AggregateMethodInvoker.php b/src/Invoker/Impl/AggregateMethodInvoker.php index a3fec80..d834b73 100644 --- a/src/Invoker/Impl/AggregateMethodInvoker.php +++ b/src/Invoker/Impl/AggregateMethodInvoker.php @@ -10,13 +10,18 @@ final class AggregateMethodInvoker { /** - * @param iterable $invokers + * @param iterable $invokers */ public function __construct( private iterable $invokers ) { } + /** + * @template T of object + * + * @param Instance $instance + */ public function invoke(Instance $instance, ?object $object = null): mixed { if (!\is_array($this->invokers)) { diff --git a/src/Model/Event.php b/src/Model/Event.php index 768f99e..c8bf319 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -34,6 +34,11 @@ className: $class, ); } + /** + * @template T of object + * + * @param Instance $instance + */ public function getUseCaseRequest(): mixed { return $this->parameters['useCaseRequest'] ?? ($this->parameters['request'] ?? null); diff --git a/src/Model/Request/Instance.php b/src/Model/Request/Instance.php index c0ee782..8d4e029 100644 --- a/src/Model/Request/Instance.php +++ b/src/Model/Request/Instance.php @@ -7,10 +7,16 @@ use Doctrine\Common\Annotations\AnnotationException; use Doctrine\Common\Annotations\AnnotationReader; +/** + * @template T of object + */ final class Instance { private Method $method; + /** + * @var \ReflectionClass + */ private \ReflectionClass $reflection; private function __construct() @@ -18,10 +24,13 @@ private function __construct() } /** + * @param class-string $class * @param array|null $parameters * * @throws \ReflectionException * @throws AnnotationException + * + * @return self */ public static function createFromMethod( string $class, @@ -49,10 +58,16 @@ public function getMethod(): Method return $this->method; } + /** + * @param \ReflectionClass $reflection + * + * @return self + */ public static function create( \ReflectionClass $reflection, Method $method ): self { + /** @var Instance $self */ $self = new self(); $self->reflection = $reflection; $self->method = $method; @@ -62,6 +77,8 @@ public static function create( /** * @param array $parameters + * + * @return self */ public function setParameters(array $parameters): self { @@ -70,6 +87,9 @@ public function setParameters(array $parameters): self return $this; } + /** + * @return self + */ public function setResponse(mixed $response): self { $this->method->setResponse($response); @@ -77,6 +97,9 @@ public function setResponse(mixed $response): self return $this; } + /** + * @return \ReflectionClass + */ public function getReflection(): \ReflectionClass { return $this->reflection; diff --git a/src/Proxy/AccessInterceptorsInterface.php b/src/Proxy/AccessInterceptorsInterface.php index 964422c..8e5928a 100644 --- a/src/Proxy/AccessInterceptorsInterface.php +++ b/src/Proxy/AccessInterceptorsInterface.php @@ -1,15 +1,25 @@ - + */ interface AccessInterceptorsInterface extends ProxyInterface { - + /** + * @param array, object, string, array, bool): mixed>> $prefixInterceptors + */ public function setPrefixInterceptors(array $prefixInterceptors): void; + /** + * @param array, object, string, array, bool): mixed>> $suffixInterceptors + */ public function setSuffixInterceptors(array $suffixInterceptors): void; } diff --git a/src/ProxyFactory.php b/src/ProxyFactory.php index e89dd9f..a76de2a 100644 --- a/src/ProxyFactory.php +++ b/src/ProxyFactory.php @@ -8,8 +8,6 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\Reader; use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Factory\AccessInterceptorFactory; -use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorScopeLocalizerGenerator\Factory\AccessInterceptorScopeLocalizerFactory; -use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorValueHolderGenerator\Factory\AccessInterceptorValueHolderFactory; use OpenClassrooms\ServiceProxy\Interceptor\Contract\PrefixInterceptor; use OpenClassrooms\ServiceProxy\Interceptor\Contract\SuffixInterceptor; use OpenClassrooms\ServiceProxy\Model\Request\Instance; @@ -61,7 +59,7 @@ public function __construct( * @template T of object * * @param class-string $class - * @param mixed ...$agrs + * @param mixed ...$args * * @return T */ @@ -70,7 +68,10 @@ public function createInstance(string $class, ...$args): object $instanceRef = new \ReflectionClass($class); if ($instanceRef->isFinal()) { - throw new \LogicException(sprintf("Unable to proxify final class %s. Hint: replace final keywords with a @final annotation", $instanceRef->getName())); + throw new \LogicException(sprintf( + 'Unable to proxify final class %s. Hint: replace final keywords with a @final annotation', + $instanceRef->getName() + )); } $methods = $instanceRef->getMethods(); @@ -78,7 +79,7 @@ public function createInstance(string $class, ...$args): object foreach ($methods as $methodRef) { $methodAnnotations = $this->annotationReader->getMethodAnnotations($methodRef); - if (count($methodAnnotations) === 0 && count($methodRef->getAttributes()) === 0) { + if (\count($methodAnnotations) === 0 && \count($methodRef->getAttributes()) === 0) { continue; } @@ -93,11 +94,19 @@ public function createInstance(string $class, ...$args): object $interceptors = $this->filterInterceptors($instance, $type); if (\count($interceptors) > 0) { if ($methodRef->isFinal()) { - throw new \LogicException(sprintf("Unable to proxify a final method %s on class %s. Hint: replace final keywords with a @final annotation", $methodRef->getName(), $methodRef->getDeclaringClass())); + throw new \LogicException(sprintf( + 'Unable to proxify a final method %s on class %s. Hint: replace final keywords with a @final annotation', + $methodRef->getName(), + $methodRef->getDeclaringClass() + )); } if ($methodRef->isPrivate()) { - throw new \LogicException(sprintf("Unable to attach an interceptor to a private method %s on class %s.", $methodRef->getName(), $methodRef->getDeclaringClass())); + throw new \LogicException(sprintf( + 'Unable to attach an interceptor to a private method %s on class %s.', + $methodRef->getName(), + $methodRef->getDeclaringClass() + )); } $interceptionClosures[$type][$methodRef->getName()] = $this->getInterceptionClosure( @@ -132,8 +141,11 @@ public function getInterceptors(): array } /** + * @template T of object + * * @param PrefixInterceptor::PREFIX_TYPE|SuffixInterceptor::SUFFIX_TYPE $type * @param PrefixInterceptor[]|SuffixInterceptor[] $interceptors + * @param Instance $instance */ private function intercept( string $type, @@ -200,6 +212,10 @@ static function (object $a, object $b) use ($type) { } /** + * @template T of object + * + * @param Instance $instance + * / * @param PrefixInterceptor::PREFIX_TYPE|SuffixInterceptor::SUFFIX_TYPE $type * * @return PrefixInterceptor[]|SuffixInterceptor[] @@ -227,8 +243,10 @@ private function filterInterceptors(Instance $instance, string $type): array } /** + * @template T of object * @param PrefixInterceptor::PREFIX_TYPE|SuffixInterceptor::SUFFIX_TYPE $type * @param SuffixInterceptor[]|PrefixInterceptor[] $interceptors + * @param Instance $instance */ private function getInterceptionClosure( string $type, diff --git a/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php b/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php index 895aba3..ce6bda3 100644 --- a/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php +++ b/tests/Double/Stub/ClassWithAnnotationOnPrivateMethod.php @@ -1,5 +1,7 @@ assertNotProxy(WithoutAnnotationClass::class, $instance); } - public function testThrownExceptionWhenCreateAProxyOnFinalClass(): void { $this->expectException(\LogicException::class); diff --git a/tests/ProxyTestTrait.php b/tests/ProxyTestTrait.php index 00292e1..588cee7 100644 --- a/tests/ProxyTestTrait.php +++ b/tests/ProxyTestTrait.php @@ -10,8 +10,6 @@ use OpenClassrooms\ServiceProxy\ProxyFactory; use OpenClassrooms\ServiceProxy\ProxyFactoryConfiguration; use PHPUnit\Framework\TestCase as Assert; -use ProxyManager\Proxy\AccessInterceptorInterface; -use ProxyManager\Proxy\ValueHolderInterface; use Symfony\Component\Filesystem\Filesystem; trait ProxyTestTrait From b496611408c60f53dd8ed85291bda28d04f32d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Tue, 26 Mar 2024 09:48:20 +0100 Subject: [PATCH 03/15] Clarrify error message --- .../AccessInterceptorGenerator.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php index 18b579f..5e5da37 100644 --- a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php +++ b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php @@ -18,6 +18,10 @@ use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionMethod; + class AccessInterceptorGenerator implements ProxyGeneratorInterface { /** @@ -25,13 +29,13 @@ class AccessInterceptorGenerator implements ProxyGeneratorInterface * * @param array{'methods'?: array} $proxyOptions * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException * @throws InvalidProxiedClassException */ - public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) { if (!\array_key_exists('methods', $proxyOptions)) { - throw new \InvalidArgumentException(sprintf('Generator %s needs a methods proxyOptions', __CLASS__)); + throw new InvalidArgumentException(sprintf('Missing methods options for %s.', __CLASS__)); } CanProxyAssertion::assertClassCanBeProxied($originalClass, false); @@ -65,7 +69,7 @@ private function buildMethodInterceptor( MethodPrefixInterceptors $prefixInterceptors, MethodSuffixInterceptors $suffixInterceptors ): callable { - return static function (\ReflectionMethod $method) use ( + return static function (ReflectionMethod $method) use ( $prefixInterceptors, $suffixInterceptors ): InterceptedMethod { From e1f392d4ca5278d6d96ddd7a2745b1486187da8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 3 Apr 2024 18:13:56 +0200 Subject: [PATCH 04/15] Small fixes from review --- .../Compiler/ServiceProxyPass.php | 4 +++- .../AccessInterceptorGenerator.php | 12 ++++-------- .../Cache/ClassWithInvalidateCacheAttributes.php | 2 -- tests/Double/Stub/FinalClass.php | 3 +++ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php index b45a97d..7abbf3e 100644 --- a/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php +++ b/src/FrameworkBridge/Symfony/DependencyInjection/Compiler/ServiceProxyPass.php @@ -44,7 +44,9 @@ private function overrideServiceDefinition(string $taggedServiceName): void if ($definition->getFactory() !== null) { $this->compiler->log($this, "Service {$taggedServiceName} is not compatible with service proxy"); - return; + throw new \RuntimeException( + "Unable to override {$taggedServiceName}, remove the factory definition or the Interceptable interface." + ); } $definition->setFactory([new Reference(ProxyFactory::class), 'createInstance']); diff --git a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php index 5e5da37..c163335 100644 --- a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php +++ b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php @@ -18,10 +18,6 @@ use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion; use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; -use InvalidArgumentException; -use ReflectionClass; -use ReflectionMethod; - class AccessInterceptorGenerator implements ProxyGeneratorInterface { /** @@ -29,13 +25,13 @@ class AccessInterceptorGenerator implements ProxyGeneratorInterface * * @param array{'methods'?: array} $proxyOptions * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * @throws InvalidProxiedClassException */ - public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) + public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) { if (!\array_key_exists('methods', $proxyOptions)) { - throw new InvalidArgumentException(sprintf('Missing methods options for %s.', __CLASS__)); + throw new \InvalidArgumentException(sprintf('Missing methods options for %s.', __CLASS__)); } CanProxyAssertion::assertClassCanBeProxied($originalClass, false); @@ -69,7 +65,7 @@ private function buildMethodInterceptor( MethodPrefixInterceptors $prefixInterceptors, MethodSuffixInterceptors $suffixInterceptors ): callable { - return static function (ReflectionMethod $method) use ( + return static function (\ReflectionMethod $method) use ( $prefixInterceptors, $suffixInterceptors ): InterceptedMethod { diff --git a/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php b/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php index b3ff466..66017f7 100644 --- a/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php +++ b/tests/Double/Stub/Cache/ClassWithInvalidateCacheAttributes.php @@ -11,8 +11,6 @@ class ClassWithInvalidateCacheAttributes { public const DATA = 'data'; - private $data; - #[Cache(tags: ['"my_tag"'])] public function methodWithTaggedCache(): string { diff --git a/tests/Double/Stub/FinalClass.php b/tests/Double/Stub/FinalClass.php index afaa3dc..ba55962 100644 --- a/tests/Double/Stub/FinalClass.php +++ b/tests/Double/Stub/FinalClass.php @@ -4,8 +4,11 @@ namespace OpenClassrooms\ServiceProxy\Tests\Double\Stub; +use OpenClassrooms\ServiceProxy\Attribute\Cache; + final class FinalClass { + #[Cache] public function aMethod(): void { } From 150fc8bf5b53117fe3d4b4f51068f270c68c09c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Fri, 28 Nov 2025 15:27:47 +0100 Subject: [PATCH 05/15] Fix deprectaed and tests --- src/Handler/Contract/EventHandler.php | 2 +- src/Handler/Impl/Cache/DoctrineCacheHandler.php | 4 ++-- tests/Interceptor/EventInterceptorTest.php | 4 ++-- tests/Interceptor/LockInterceptorTest.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Handler/Contract/EventHandler.php b/src/Handler/Contract/EventHandler.php index 01b6f38..95725b2 100644 --- a/src/Handler/Contract/EventHandler.php +++ b/src/Handler/Contract/EventHandler.php @@ -16,5 +16,5 @@ public function dispatch(object $event, ?string $queue = null, ?int $delay = nul * * @param Instance $instance */ - public function listen(Instance $instance, string $name, Transport $transport = null, int $priority = 0): void; + public function listen(Instance $instance, string $name, ?Transport $transport = null, int $priority = 0): void; } diff --git a/src/Handler/Impl/Cache/DoctrineCacheHandler.php b/src/Handler/Impl/Cache/DoctrineCacheHandler.php index 964d0e9..c63486a 100644 --- a/src/Handler/Impl/Cache/DoctrineCacheHandler.php +++ b/src/Handler/Impl/Cache/DoctrineCacheHandler.php @@ -19,7 +19,7 @@ final class DoctrineCacheHandler implements CacheHandler private CacheItemPoolInterface $pool; - public function __construct(CacheItemPoolInterface $pool = null, ?string $name = null) + public function __construct(?CacheItemPoolInterface $pool = null, ?string $name = null) { $this->pool = $pool ?? new ArrayAdapter(storeSerialized: false); $this->name = $name; @@ -67,7 +67,7 @@ public function getName(): string return $this->name ?? 'doctrine_array'; } - private function fetchWithNamespace(string $id, string $namespaceId = null): CacheItemInterface + private function fetchWithNamespace(string $id, ?string $namespaceId = null): CacheItemInterface { if ($namespaceId !== null) { $namespace = $this->doFetch($namespaceId); diff --git a/tests/Interceptor/EventInterceptorTest.php b/tests/Interceptor/EventInterceptorTest.php index ebc2481..1701183 100644 --- a/tests/Interceptor/EventInterceptorTest.php +++ b/tests/Interceptor/EventInterceptorTest.php @@ -121,7 +121,7 @@ public function testMultiEventsSendMultiple(): void public function testMessageClassEventDispatchedWithObjectResponse(): void { - $proxy = $this->proxyFactory->createProxy(new ObjectResponseMessageClassAnnotatedClass()); + $proxy = $this->proxyFactory->createInstance(ObjectResponseMessageClassAnnotatedClass::class); $result = $proxy->handle('world'); $this->assertInstanceOf(ResponseObject::class, $result); @@ -146,7 +146,7 @@ public function testMessageClassEventDispatchedWithObjectResponse(): void public function testInvalidResponseForMessageClassThrowsException(): void { - $proxy = $this->proxyFactory->createProxy(new InvalidResponseMessageClassAnnotatedClass()); + $proxy = $this->proxyFactory->createInstance(InvalidResponseMessageClassAnnotatedClass::class); $this->expectException(\InvalidArgumentException::class); $proxy->invalid('test'); diff --git a/tests/Interceptor/LockInterceptorTest.php b/tests/Interceptor/LockInterceptorTest.php index 9c4151f..9080ca0 100644 --- a/tests/Interceptor/LockInterceptorTest.php +++ b/tests/Interceptor/LockInterceptorTest.php @@ -39,7 +39,7 @@ protected function setUp(): void ), ] ); - $this->proxy = $this->proxyFactory->createProxy(new LockAnnotatedStub()); + $this->proxy = $this->proxyFactory->createInstance(LockAnnotatedStub::class); } public function test(): void From effa0c9b2342b84bdf9576fe63d9205380430ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 3 Dec 2025 11:21:02 +0100 Subject: [PATCH 06/15] Add helper method ofr instance type checking --- src/Model/Request/Instance.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Model/Request/Instance.php b/src/Model/Request/Instance.php index 8d4e029..2ff9143 100644 --- a/src/Model/Request/Instance.php +++ b/src/Model/Request/Instance.php @@ -104,4 +104,12 @@ public function getReflection(): \ReflectionClass { return $this->reflection; } + + /** + * @param class-string $class + */ + public function isInstanceOf(string $class): bool + { + return $this->reflection->isSubclassOf($class); + } } From 1a3aa9525e57545597bb0576dab1ed012e9c5b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 10 Dec 2025 15:06:48 +0100 Subject: [PATCH 07/15] Update CI --- .github/workflows/analysis.yaml | 111 +++++++++++++++++--------------- composer.json | 36 +++++------ 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml index b27370e..47194cc 100644 --- a/.github/workflows/analysis.yaml +++ b/.github/workflows/analysis.yaml @@ -1,58 +1,63 @@ name: Code Analysis on: - pull_request: ~ - push: - branches: - - main + pull_request: ~ + push: + branches: + - main jobs: - code-validation: - name: ${{ matrix.actions.name }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - actions: - - name: Composer validate - run: composer validate --ansi --strict - - - name: Static analysis - run: composer phpstan - - - name: Style check - run: composer check-cs - - - name: Unit tests - run: composer coverage - steps: - - uses: actions/checkout@v2 - - - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - coverage: pcov - tools: composer:v2 - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install dependencies - uses: "ramsey/composer-install@v2" - - - run: ${{ matrix.actions.run }} - - - name: Coverage diff - uses: OpenClassrooms/coverage-checker@v1.7.0 - if: ${{ hashFiles('build/coverage/coverage.xml') != '' && github.event_name == 'pull_request' }} - with: - action: check - files: '[{"coverage": "build/coverage/coverage.xml", "summary": "coverage-summary.json", "label": "Coverage", "badge": "coverage.svg"}]' - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Coverage update - uses: OpenClassrooms/coverage-checker@v1.7.0 - if: ${{ hashFiles('build/coverage/coverage.xml') != '' && github.event_name == 'push' && github.ref_name == 'main' }} - with: - action: update - files: '[{"coverage": "build/coverage/coverage.xml", "summary": "coverage-summary.json", "label": "Coverage", "badge": "coverage.svg"}]' - token: ${{ secrets.GITHUB_TOKEN }} + code-validation: + name: ${{ matrix.actions.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [8.3, 8.4, 8.5] + symfony: [6.5, 7.4, 8.0] + actions: + - name: Composer validate + run: composer validate --ansi --strict + + - name: Static analysis + run: composer phpstan + + - name: Style check + run: composer check-cs + + - name: Unit tests + run: composer coverage + steps: + - uses: actions/checkout@v6 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: pcov + tools: composer:v2 + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + shell: bash + run: + if [ "${{ matrix.symfony }}" != "" ]; then composer require "symfony/filesystem:${{ matrix.symfony }}" --no-update --no-interaction --prefer-dist --optimize-autoloader; fi; + composer install --no-interaction --prefer-dist --optimize-autoloader + + - run: ${{ matrix.actions.run }} + + - name: Coverage diff + uses: OpenClassrooms/coverage-checker@v1.7.0 + if: ${{ hashFiles('build/coverage/coverage.xml') != '' && github.event_name == 'pull_request' }} + with: + action: check + files: '[{"coverage": "build/coverage/coverage.xml", "summary": "coverage-summary.json", "label": "Coverage", "badge": "coverage.svg"}]' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Coverage update + uses: OpenClassrooms/coverage-checker@v1.7.0 + if: ${{ hashFiles('build/coverage/coverage.xml') != '' && github.event_name == 'push' && github.ref_name == 'main' }} + with: + action: update + files: '[{"coverage": "build/coverage/coverage.xml", "summary": "coverage-summary.json", "label": "Coverage", "badge": "coverage.svg"}]' + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/composer.json b/composer.json index a4d37c9..9eeb77f 100644 --- a/composer.json +++ b/composer.json @@ -26,12 +26,12 @@ } }, "require": { - "php": ">=8.1", - "doctrine/annotations": "^1.2 || ^2.0" , + "php": "^8.3", + "doctrine/annotations": "^2.0" , "friendsofphp/proxy-manager-lts": "^1.0", - "symfony/expression-language": "^5.0 || ^6.0", - "symfony/filesystem": "^5.0 || ^6.0", - "symfony/property-info": "^5.0 || ^6.0", + "symfony/expression-language": "^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.0 || ^7.0 || ^8.0", + "symfony/property-info": "^6.0 || ^7.0 || ^8.0", "phpdocumentor/reflection-docblock": "^5.0", "phpstan/phpdoc-parser": "^1.24", "webmozart/assert": "^1.11", @@ -50,20 +50,20 @@ "korbeil/phpstan-generic-rules": "^1.0", "ergebnis/phpstan-rules": "^1.0", "symplify/easy-coding-standard": "^11.2", - "symfony/dependency-injection": "^5.0 || ^6.0", - "symfony/config": "^5.0 || ^6.0", - "symfony/cache": "^5.0 || ^6.0", - "symfony/http-kernel": "^5.0 || ^6.0", - "symfony/event-dispatcher": "^5.0 || ^6.0", - "symfony/security-bundle": "^5.0 || ^6.0", - "symfony/messenger": "^5.0 || ^6.0", + "symfony/dependency-injection": "^6.0 || ^7.0 || ^8.0", + "symfony/config": "^6.0 || ^7.0 || ^8.0", + "symfony/cache": "^6.0 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.0 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^6.0 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.0 || ^7.0 || ^8.0", + "symfony/messenger": "^6.0 || ^7.0 || ^8.0", "doctrine/orm": "~2.5 || ^3.0", - "symfony/stopwatch": "^5.0 || ^6.0", - "symfony/serializer": "^5.0 || ^6.0", - "symfony/http-client": "^5.0 || ^6.0", - "symfony/uid": "^5.0 || ^6.0", - "symfony/http-foundation": "^5.0 || ^6.0", - "symfony/lock": "^5.0 || ^6.0", + "symfony/stopwatch": "^6.0 || ^7.0 || ^8.0", + "symfony/serializer": "^6.0 || ^7.0 || ^8.0", + "symfony/http-client": "^6.0 || ^7.0 || ^8.0", + "symfony/uid": "^6.0 || ^7.0 || ^8.0", + "symfony/http-foundation": "^6.0 || ^7.0 || ^8.0", + "symfony/lock": "^6.0 || ^7.0 || ^8.0", "phpstan/phpstan-webmozart-assert": "^1.2" }, "suggest": { From d265dc3c180c47da721df69dece325fbcc732392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 10 Dec 2025 15:07:58 +0100 Subject: [PATCH 08/15] Update CI --- .github/workflows/analysis.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml index 47194cc..249b371 100644 --- a/.github/workflows/analysis.yaml +++ b/.github/workflows/analysis.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: php: [8.3, 8.4, 8.5] - symfony: [6.5, 7.4, 8.0] + symfony: [6.4, 7.4, 8.0] actions: - name: Composer validate run: composer validate --ansi --strict From 826b94a9aac910eca213c65c6f04572a03a7f507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Wed, 10 Dec 2025 15:13:35 +0100 Subject: [PATCH 09/15] Update CI --- .github/workflows/analysis.yaml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml index 249b371..fcefb5a 100644 --- a/.github/workflows/analysis.yaml +++ b/.github/workflows/analysis.yaml @@ -8,7 +8,7 @@ on: jobs: code-validation: - name: ${{ matrix.actions.name }} + name: ${{ matrix.actions.name }} - PHP ${{ matrix.php }} - Symfony ${{ matrix.symfony }} runs-on: ubuntu-latest strategy: fail-fast: false @@ -38,11 +38,18 @@ jobs: env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: symfony/flex is required to install the correct symfony version + if: ${{ matrix.symfony }} + run: | + composer global config --no-plugins allow-plugins.symfony/flex true + composer global require symfony/flex + + - name: Configure Symfony version for symfony/flex + if: ${{ matrix.symfony }} + run: composer config extra.symfony.require "${{ matrix.symfony }}.*" + - name: Install dependencies - shell: bash - run: - if [ "${{ matrix.symfony }}" != "" ]; then composer require "symfony/filesystem:${{ matrix.symfony }}" --no-update --no-interaction --prefer-dist --optimize-autoloader; fi; - composer install --no-interaction --prefer-dist --optimize-autoloader + run: composer install --no-interaction --prefer-dist --optimize-autoloader - run: ${{ matrix.actions.run }} From 206cd880da11301f7e9cbcb841f7b5494dd6025c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 09:15:04 +0100 Subject: [PATCH 10/15] Remove symfony internal class usage --- composer.json | 1 - src/Helper/TypesExtractor.php | 186 +++++++++++++++++++++------------- 2 files changed, 113 insertions(+), 74 deletions(-) diff --git a/composer.json b/composer.json index 9eeb77f..ee335c6 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,6 @@ "friendsofphp/proxy-manager-lts": "^1.0", "symfony/expression-language": "^6.0 || ^7.0 || ^8.0", "symfony/filesystem": "^6.0 || ^7.0 || ^8.0", - "symfony/property-info": "^6.0 || ^7.0 || ^8.0", "phpdocumentor/reflection-docblock": "^5.0", "phpstan/phpdoc-parser": "^1.24", "webmozart/assert": "^1.11", diff --git a/src/Helper/TypesExtractor.php b/src/Helper/TypesExtractor.php index 05238be..fb5118e 100644 --- a/src/Helper/TypesExtractor.php +++ b/src/Helper/TypesExtractor.php @@ -4,45 +4,36 @@ namespace OpenClassrooms\ServiceProxy\Helper; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; -use Symfony\Component\PropertyInfo\PhpStan\NameScope; -use Symfony\Component\PropertyInfo\PhpStan\NameScopeFactory; -use Symfony\Component\PropertyInfo\Util\PhpStanTypeHelper; final class TypesExtractor { private Lexer $lexer; - private PhpDocParser $phpDocParser; - private PhpStanTypeHelper $typeHelper; - - private NameScopeFactory $nameScopeFactory; - - /** - * @var array - */ - private array $resolvedNameScopes = []; - public function __construct() { $this->lexer = new Lexer(); - $this->nameScopeFactory = new NameScopeFactory(); - $this->typeHelper = new PhpStanTypeHelper(); - $constExprParser = new ConstExprParser(); - $this->phpDocParser = new PhpDocParser( - new TypeParser($constExprParser), - $constExprParser - ); + $this->phpDocParser = new PhpDocParser(new TypeParser($constExprParser), $constExprParser); } /** - * @return array + * @return array */ public function extractFromMethod(\ReflectionMethod $method): array { @@ -53,7 +44,7 @@ public function extractFromMethod(\ReflectionMethod $method): array $types[] = $this->getClassMembersTypes($methodType); } - return array_unique(array_merge(...$types)); + return array_unique(array_merge(...($types ?: [[]]))); } /** @@ -65,10 +56,6 @@ private function getTypes(\ReflectionMethod|\ReflectionProperty $member): array ? $member->getReturnType() : $member->getType(); - $docComment = $member->getDocComment() !== false - ? $member->getDocComment() - : null; - $types = []; if ($type !== null) { @@ -78,19 +65,17 @@ private function getTypes(\ReflectionMethod|\ReflectionProperty $member): array ); } + // Parse PHPDoc (@return, @var) with PHPStan PhpDocParser + $docComment = $member->getDocComment() !== false ? $member->getDocComment() : null; if ($docComment !== null) { + $declaringNs = $member->getDeclaringClass()->getNamespaceName(); $types = array_merge( $types, - $this->getPhpDocTypesNames( - $member->getDeclaringClass() - ->getName(), - $docComment, - ['@var', '@return'], - ) + $this->getPhpDocTypesNames($declaringNs, $docComment, ['@var', '@return']) ); } - return array_unique($types); + return array_values(array_unique($types)); } /** @@ -113,40 +98,109 @@ private function getReflectionTypeNames(\ReflectionType $type): array } /** + * Extract type names from PHPDoc tags using PHPStan PhpDocParser. * @param array $tagNames - * @param class-string $classContext - * * @return array */ - private function getPhpDocTypesNames(string $classContext, string $docComment, array $tagNames = []): array + private function getPhpDocTypesNames(string $namespace, string $docComment, array $tagNames): array { - $classNames = []; - $nameScope = $this->getNameScope($classContext); + $results = []; $tokens = new TokenIterator($this->lexer->tokenize($docComment)); - $phpDocNode = $this->phpDocParser->parse($tokens); - foreach ($tagNames as $tagName) { - $tags = $phpDocNode->getTagsByName($tagName); - foreach ($tags as $tag) { - $types = $this->typeHelper->getTypes($tag->value, $nameScope); - foreach ($types as $type) { - $classNames[] = $type->getClassName() ?? $type->getBuiltinType(); - foreach ($type->getCollectionValueTypes() as $collectionType) { - $classNames[] = $collectionType->getClassName() ?? $collectionType->getBuiltinType(); - } - foreach ($type->getCollectionKeyTypes() as $collectionType) { - $classNames[] = $collectionType->getClassName() ?? $collectionType->getBuiltinType(); - } - } + + foreach ($phpDocNode->getTags() as $tagNode) { + if (!in_array($tagNode->name, $tagNames, true)) { + continue; } + $results = array_merge($results, $this->extractFromTagNode($tagNode, $namespace)); } - return array_filter($classNames); + return array_values(array_unique(array_filter($results))); + } + + /** + * @return array + */ + private function extractFromTagNode(PhpDocTagNode $tagNode, string $namespace): array + { + $builtins = [ + 'int','string','bool','float','array','object','callable','iterable','mixed','void','null','false','true','self','static','parent' + ]; + + $typeNode = $tagNode->value instanceof ReturnTagValueNode || $tagNode->value instanceof VarTagValueNode + ? $tagNode->value->type + : null; + + if ($typeNode === null) { + return []; + } + + return $this->collectTypeNames($typeNode, $namespace, $builtins); + } + + /** + * Walk a TypeNode tree and collect class-like names. + * @param array $builtins + * @return array + */ + private function collectTypeNames($typeNode, string $namespace, array $builtins): array + { + $names = []; + + if ($typeNode instanceof IdentifierTypeNode) { + $names = array_merge($names, $this->resolveClassName($typeNode->name, $namespace, $builtins)); + } elseif ($typeNode instanceof NullableTypeNode) { + $names = array_merge($names, $this->collectTypeNames($typeNode->type, $namespace, $builtins)); + } elseif ($typeNode instanceof ArrayTypeNode) { + $names = array_merge($names, $this->collectTypeNames($typeNode->type, $namespace, $builtins)); + } elseif ($typeNode instanceof UnionTypeNode) { + foreach ($typeNode->types as $t) { + $names = array_merge($names, $this->collectTypeNames($t, $namespace, $builtins)); + } + } elseif ($typeNode instanceof IntersectionTypeNode) { + foreach ($typeNode->types as $t) { + $names = array_merge($names, $this->collectTypeNames($t, $namespace, $builtins)); + } + } elseif ($typeNode instanceof GenericTypeNode) { + $names = array_merge($names, $this->collectTypeNames($typeNode->type, $namespace, $builtins)); + foreach ($typeNode->genericTypes as $gt) { + $names = array_merge($names, $this->collectTypeNames($gt, $namespace, $builtins)); + } + } elseif ($typeNode instanceof ThisTypeNode) { + // ignore $this type + } + + return $names; + } + + /** + * @param array $builtins + * @return array + */ + private function resolveClassName(string $token, string $namespace, array $builtins): array + { + $token = trim($token); + if ($token === '') { return []; } + + $isFqcn = str_starts_with($token, '\\'); + $normalized = $isFqcn ? substr($token, 1) : $token; + if (in_array(strtolower($normalized), $builtins, true)) { + return []; + } + + if ($isFqcn || str_contains($normalized, '\\')) { + return [$normalized]; + } + + if ($namespace !== '') { + return [$namespace . '\\' . $normalized]; + } + + return [$normalized]; } /** * @param string[] $registeredTypes - * * @return string[] */ private function getClassMembersTypes(string $type, array $registeredTypes = []): array @@ -159,12 +213,10 @@ private function getClassMembersTypes(string $type, array $registeredTypes = []) $ref = new \ReflectionClass($type); - $subTypes = $this->getMembersTypes( - [ - ...$ref->getMethods(\ReflectionMethod::IS_PUBLIC), - ...$ref->getProperties(\ReflectionProperty::IS_PUBLIC), - ] - ); + $subTypes = $this->getMembersTypes([ + ...$ref->getMethods(\ReflectionMethod::IS_PUBLIC), + ...$ref->getProperties(\ReflectionProperty::IS_PUBLIC), + ]); foreach ($subTypes as $subType) { $registeredTypes = $this->getClassMembersTypes($subType, $registeredTypes); @@ -184,18 +236,6 @@ private function getMembersTypes(array $members): array $types[] = $this->getTypes($member); } - return array_unique(array_merge(...$types)); - } - - /** - * @param class-string $className - */ - private function getNameScope(string $className): NameScope - { - if (!isset($this->resolvedNameScopes[$className])) { - $this->resolvedNameScopes[$className] = $this->nameScopeFactory->create($className); - } - - return $this->resolvedNameScopes[$className]; + return array_unique(array_merge(...($types ?: [[]]))); } } From c74b425b9deaeeef7bb57ce1fdffb90d7373a9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 12:34:24 +0100 Subject: [PATCH 11/15] Upgrade phpstan --- .github/workflows/analysis.yaml | 2 +- composer.json | 52 +++++++++---------- phpstan.neon | 5 +- src/Annotation/Annotation.php | 6 +-- src/Attribute/Cache.php | 4 +- src/Attribute/Event.php | 10 ++-- src/Attribute/Event/Listen.php | 6 +-- src/Attribute/InvalidateCache.php | 2 +- .../Symfony/CacheWarmer/ProxyCacheWarmer.php | 6 ++- .../OpenClassroomsServiceProxyExtension.php | 2 +- .../Serialization/MessageSerializer.php | 4 +- .../Subscriber/ServiceProxySubscriber.php | 4 +- .../AccessInterceptorGenerator.php | 13 ++--- .../Method/SetMethodPrefixInterceptors.php | 2 - .../Method/SetMethodSuffixInterceptors.php | 2 - .../Impl/Cache/DoctrineCacheHandler.php | 2 +- .../Impl/Cache/SymfonyCacheHandler.php | 2 +- src/Handler/Impl/Event/HttpEventHandler.php | 1 + .../Event/SymfonyMessengerEventHandler.php | 6 +-- src/Helper/TypesExtractor.php | 36 +++++++------ .../Contract/Event/EventFactory.php | 1 + .../InternalCodeRetrievalException.php | 2 +- src/Interceptor/Impl/CacheInterceptor.php | 12 +++-- .../Impl/Event/ServiceProxyEventFactory.php | 1 + src/Interceptor/Impl/EventInterceptor.php | 8 ++- .../Impl/InvalidateCacheInterceptor.php | 2 +- src/Interceptor/Impl/LockInterceptor.php | 11 +++- src/Interceptor/Impl/SecurityInterceptor.php | 12 ++--- .../Impl/TransactionInterceptor.php | 4 -- src/Model/Event.php | 13 +---- src/Model/Message.php | 2 +- src/Model/Request/Method.php | 2 +- src/ProxyFactory.php | 20 +++---- 33 files changed, 131 insertions(+), 126 deletions(-) diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml index fcefb5a..4049752 100644 --- a/.github/workflows/analysis.yaml +++ b/.github/workflows/analysis.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: php: [8.3, 8.4, 8.5] - symfony: [6.4, 7.4, 8.0] + symfony: [6.4, 7.4] actions: - name: Composer validate run: composer validate --ansi --strict diff --git a/composer.json b/composer.json index ee335c6..17b15f2 100644 --- a/composer.json +++ b/composer.json @@ -29,41 +29,41 @@ "php": "^8.3", "doctrine/annotations": "^2.0" , "friendsofphp/proxy-manager-lts": "^1.0", - "symfony/expression-language": "^6.0 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.0 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.0 || ^7.0", + "symfony/filesystem": "^6.0 || ^7.0", "phpdocumentor/reflection-docblock": "^5.0", - "phpstan/phpdoc-parser": "^1.24", + "phpstan/phpdoc-parser": "^2.3", "webmozart/assert": "^1.11", "jolicode/automapper": "^9.2" }, "require-dev": { "phpunit/phpunit": "^9.5", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-strict-rules": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "roave/security-advisories": "dev-latest", "phpstan/extension-installer": "^1.2", "ergebnis/composer-normalize": "^2.28", - "thecodingmachine/phpstan-strict-rules": "^1.0", - "korbeil/phpstan-generic-rules": "^1.0", - "ergebnis/phpstan-rules": "^1.0", - "symplify/easy-coding-standard": "^11.2", - "symfony/dependency-injection": "^6.0 || ^7.0 || ^8.0", - "symfony/config": "^6.0 || ^7.0 || ^8.0", - "symfony/cache": "^6.0 || ^7.0 || ^8.0", - "symfony/http-kernel": "^6.0 || ^7.0 || ^8.0", - "symfony/event-dispatcher": "^6.0 || ^7.0 || ^8.0", - "symfony/security-bundle": "^6.0 || ^7.0 || ^8.0", - "symfony/messenger": "^6.0 || ^7.0 || ^8.0", + "kcs/phpstan-strict-rules": "^2.0", + "korbeil/phpstan-generic-rules": "^2.1", + "ergebnis/phpstan-rules": "^2.0", + "symplify/easy-coding-standard": "^13.0", + "symfony/dependency-injection": "^6.0 || ^7.0", + "symfony/config": "^6.0 || ^7.0", + "symfony/cache": "^6.0 || ^7.0", + "symfony/http-kernel": "^6.0 || ^7.0", + "symfony/event-dispatcher": "^6.0 || ^7.0", + "symfony/security-bundle": "^6.0 || ^7.0", + "symfony/messenger": "^6.0 || ^7.0", "doctrine/orm": "~2.5 || ^3.0", - "symfony/stopwatch": "^6.0 || ^7.0 || ^8.0", - "symfony/serializer": "^6.0 || ^7.0 || ^8.0", - "symfony/http-client": "^6.0 || ^7.0 || ^8.0", - "symfony/uid": "^6.0 || ^7.0 || ^8.0", - "symfony/http-foundation": "^6.0 || ^7.0 || ^8.0", - "symfony/lock": "^6.0 || ^7.0 || ^8.0", - "phpstan/phpstan-webmozart-assert": "^1.2" + "symfony/stopwatch": "^6.0 || ^7.0", + "symfony/serializer": "^6.0 || ^7.0", + "symfony/http-client": "^6.0 || ^7.0", + "symfony/uid": "^6.0 || ^7.0", + "symfony/http-foundation": "^6.0 || ^7.0", + "symfony/lock": "^6.0 || ^7.0", + "phpstan/phpstan-webmozart-assert": "^2.0" }, "suggest": { "symfony/dependency-injection": "For Symfony integration", @@ -118,7 +118,7 @@ ], "unit": "vendor/bin/phpunit -c phpunit.xml.dist --testdox", "coverage": "vendor/bin/phpunit -c phpunit.xml.dist --testdox --coverage-clover build/coverage/coverage.xml", - "phpstan": "vendor/bin/phpstan analyse -c phpstan.neon --ansi", + "phpstan": "vendor/bin/phpstan analyse --memory-limit=512M -c phpstan.neon --ansi", "ecs": "@check-cs" } } diff --git a/phpstan.neon b/phpstan.neon index bdf7148..58a272a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -18,8 +18,11 @@ parameters: - '/Method .+ServiceProxy.+ has a parameter \$container/' - '/Call to static method Webmozart\\Assert\\Assert::allIsInstanceOf\(\) with/' - '/Function compact\(\) should not be used/' - - '/In method .+, caught "Throwable" must be rethrown. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception./' - '/Class OpenClassrooms\\ServiceProxy\\Model\\Event is neither abstract nor final./' + - '/Closure has parameter .* that is passed by reference./' + - '/Method .* has parameter .* that is passed by reference./' + - '/Call to function array_filter\(\) requires parameter #2 to be passed to avoid loose comparison semantics./' + - '/Binary operation "." between .* results in an error./' exceptions: check: missingCheckedExceptionInThrows: true diff --git a/src/Annotation/Annotation.php b/src/Annotation/Annotation.php index d6d8e06..366a173 100644 --- a/src/Annotation/Annotation.php +++ b/src/Annotation/Annotation.php @@ -34,7 +34,7 @@ public function __construct(array $data = []) final public function __get(string $name): void { throw new \BadMethodCallException( - sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + \sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) ); } @@ -44,7 +44,7 @@ final public function __get(string $name): void final public function __isset(string $name): bool { throw new \BadMethodCallException( - sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + \sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) ); } @@ -54,7 +54,7 @@ final public function __isset(string $name): bool final public function __set(string $name, mixed $value): void { throw new \BadMethodCallException( - sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) + \sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) ); } diff --git a/src/Attribute/Cache.php b/src/Attribute/Cache.php index 486bf20..1e7c068 100644 --- a/src/Attribute/Cache.php +++ b/src/Attribute/Cache.php @@ -13,9 +13,9 @@ final class Cache extends Attribute * @param array $tags */ public function __construct( - array|string|null $handler = null, + array|string|null $handler = null, public readonly array $pools = [], - public readonly ?int $ttl = null, + public readonly ?int $ttl = null, public readonly array $tags = [], ) { parent::__construct(); diff --git a/src/Attribute/Event.php b/src/Attribute/Event.php index 5db165a..9614180 100644 --- a/src/Attribute/Event.php +++ b/src/Attribute/Event.php @@ -17,17 +17,17 @@ final class Event extends Attribute * @param class-string|null $messageClass */ public function __construct( - array|string|null $handler = null, - array|string|null $transport = null, + array|string|null $handler = null, + array|string|null $transport = null, public readonly ?string $name = null, - public readonly ?string $queue = null, - public readonly array $dispatch = [On::POST], + public readonly ?string $queue = null, + public readonly array $dispatch = [On::POST], public readonly ?string $messageClass = null, public readonly ?int $delay = null, ) { parent::__construct(); Assert::allIsInstanceOf($dispatch, On::class); - $this->setHandlers(aliases: compact('handler', 'transport')); + $this->setHandlers(null, compact('handler', 'transport')); } public function isOnException(): bool diff --git a/src/Attribute/Event/Listen.php b/src/Attribute/Event/Listen.php index f3e2f2b..b319f7d 100644 --- a/src/Attribute/Event/Listen.php +++ b/src/Attribute/Event/Listen.php @@ -14,9 +14,9 @@ final class Listen extends Attribute */ public function __construct( public readonly string $name, - ?array $handler = null, - public ?Transport $transport = null, - public readonly int $priority = 0, + ?array $handler = null, + public ?Transport $transport = null, + public readonly int $priority = 0, ) { $this->setHandlers($handler); parent::__construct(); diff --git a/src/Attribute/InvalidateCache.php b/src/Attribute/InvalidateCache.php index 02f4359..66e9362 100644 --- a/src/Attribute/InvalidateCache.php +++ b/src/Attribute/InvalidateCache.php @@ -13,7 +13,7 @@ final class InvalidateCache extends Attribute * @param array $tags */ public function __construct( - array|string|null $handler = null, + array|string|null $handler = null, public readonly array $pools = [], public readonly array $tags = [], ) { diff --git a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php index 7d106ab..9c48335 100644 --- a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php +++ b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php @@ -25,8 +25,10 @@ public function __construct(iterable $proxies) /** * {@inheritdoc} + * @param string $cacheDir + * @param string|null $buildDir */ - public function warmUp(string $cacheDir): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { foreach ($this->proxies as $proxy) { if ($proxy instanceof LazyLoadingInterface && !$proxy->isProxyInitialized()) { @@ -46,7 +48,7 @@ public function warmUp(string $cacheDir): array /** * {@inheritdoc} */ - public function isOptional() + public function isOptional(): bool { return true; } diff --git a/src/FrameworkBridge/Symfony/DependencyInjection/OpenClassroomsServiceProxyExtension.php b/src/FrameworkBridge/Symfony/DependencyInjection/OpenClassroomsServiceProxyExtension.php index 1282947..727a899 100644 --- a/src/FrameworkBridge/Symfony/DependencyInjection/OpenClassroomsServiceProxyExtension.php +++ b/src/FrameworkBridge/Symfony/DependencyInjection/OpenClassroomsServiceProxyExtension.php @@ -14,9 +14,9 @@ use OpenClassrooms\ServiceProxy\Invoker\Contract\MethodInvoker; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator; final class OpenClassroomsServiceProxyExtension extends Extension { diff --git a/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php b/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php index 73e671c..46865a3 100644 --- a/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php +++ b/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php @@ -11,14 +11,14 @@ final class MessageSerializer extends Serializer { /** - * @return array + * @return array{body: string, headers?: array} */ public function encode(Envelope $envelope): array { $result = parent::encode($envelope); $message = $envelope->getMessage(); if ($message instanceof Message) { - $result['headers'] = [...$result['headers'], ...$message->headers]; + $result['headers'] = [...($result['headers'] ?? []), ...$message->headers]; } return $result; diff --git a/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php b/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php index 59112d1..44e5f96 100644 --- a/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php +++ b/src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php @@ -29,8 +29,8 @@ final class ServiceProxySubscriber implements EventSubscriberInterface */ public function __construct( private readonly iterable $proxies, - iterable $startUpInterceptors, - Reader|null $annotationReader = null, + iterable $startUpInterceptors, + Reader|null $annotationReader = null, ) { if (!\is_array($startUpInterceptors)) { $this->startUpInterceptors = iterator_to_array($startUpInterceptors); diff --git a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php index c163335..5ac92d0 100644 --- a/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php +++ b/src/Generator/AccessInterceptorGenerator/AccessInterceptorGenerator.php @@ -31,7 +31,7 @@ class AccessInterceptorGenerator implements ProxyGeneratorInterface public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []) { if (!\array_key_exists('methods', $proxyOptions)) { - throw new \InvalidArgumentException(sprintf('Missing methods options for %s.', __CLASS__)); + throw new \InvalidArgumentException(\sprintf('Missing methods options for %s.', __CLASS__)); } CanProxyAssertion::assertClassCanBeProxied($originalClass, false); @@ -65,15 +65,10 @@ private function buildMethodInterceptor( MethodPrefixInterceptors $prefixInterceptors, MethodSuffixInterceptors $suffixInterceptors ): callable { - return static function (\ReflectionMethod $method) use ( + return static fn (\ReflectionMethod $method): InterceptedMethod => InterceptedMethod::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), $prefixInterceptors, $suffixInterceptors - ): InterceptedMethod { - return InterceptedMethod::generateMethod( - new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), - $prefixInterceptors, - $suffixInterceptors - ); - }; + ); } } diff --git a/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php index 3fd2538..93bb4c4 100644 --- a/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php +++ b/src/Generator/AccessInterceptorGenerator/Method/SetMethodPrefixInterceptors.php @@ -16,8 +16,6 @@ class SetMethodPrefixInterceptors extends MethodGenerator { /** - * Constructor - * * @throws InvalidArgumentException */ public function __construct(PropertyGenerator $prefixInterceptor) diff --git a/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php b/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php index 51e0675..6991565 100644 --- a/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php +++ b/src/Generator/AccessInterceptorGenerator/Method/SetMethodSuffixInterceptors.php @@ -16,8 +16,6 @@ class SetMethodSuffixInterceptors extends MethodGenerator { /** - * Constructor - * * @throws InvalidArgumentException */ public function __construct(PropertyGenerator $suffixInterceptor) diff --git a/src/Handler/Impl/Cache/DoctrineCacheHandler.php b/src/Handler/Impl/Cache/DoctrineCacheHandler.php index c63486a..99a0d01 100644 --- a/src/Handler/Impl/Cache/DoctrineCacheHandler.php +++ b/src/Handler/Impl/Cache/DoctrineCacheHandler.php @@ -21,7 +21,7 @@ final class DoctrineCacheHandler implements CacheHandler public function __construct(?CacheItemPoolInterface $pool = null, ?string $name = null) { - $this->pool = $pool ?? new ArrayAdapter(storeSerialized: false); + $this->pool = $pool ?? new ArrayAdapter(0, false); $this->name = $name; } diff --git a/src/Handler/Impl/Cache/SymfonyCacheHandler.php b/src/Handler/Impl/Cache/SymfonyCacheHandler.php index 9efbe0d..63904ff 100644 --- a/src/Handler/Impl/Cache/SymfonyCacheHandler.php +++ b/src/Handler/Impl/Cache/SymfonyCacheHandler.php @@ -63,7 +63,7 @@ private function getPool(string $poolName): TagAwareAdapterInterface if (!isset($existingPools[$poolName])) { throw new \InvalidArgumentException( - sprintf( + \sprintf( 'No cache pool found for "%s". Available pools are: "%s".', $poolName, implode('", "', array_keys($existingPools)) diff --git a/src/Handler/Impl/Event/HttpEventHandler.php b/src/Handler/Impl/Event/HttpEventHandler.php index b518fab..8d61242 100644 --- a/src/Handler/Impl/Event/HttpEventHandler.php +++ b/src/Handler/Impl/Event/HttpEventHandler.php @@ -38,6 +38,7 @@ public function __construct( * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface + * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ public function dispatch(object $event, ?string $queue = null, ?int $delay = null): void { diff --git a/src/Handler/Impl/Event/SymfonyMessengerEventHandler.php b/src/Handler/Impl/Event/SymfonyMessengerEventHandler.php index a6554a4..9120163 100644 --- a/src/Handler/Impl/Event/SymfonyMessengerEventHandler.php +++ b/src/Handler/Impl/Event/SymfonyMessengerEventHandler.php @@ -24,8 +24,8 @@ final class SymfonyMessengerEventHandler implements EventHandler public function __construct( private readonly MessageBusInterface $bus, - private readonly ?RequestStack $request = null, - ?LoggerInterface $logger = null + private readonly ?RequestStack $request = null, + ?LoggerInterface $logger = null ) { $this->logger = $logger ?? new NullLogger(); } @@ -44,7 +44,7 @@ public function dispatch(object $event, ?string $queue = null, ?int $delay = nul } else { $this->bus->dispatch($message); } - } catch (\Throwable $exception) { + } catch (\Throwable $exception) { // @ignoreException $this->logger->error($exception->getMessage(), compact('message', 'exception')); } } diff --git a/src/Helper/TypesExtractor.php b/src/Helper/TypesExtractor.php index fb5118e..b5c7102 100644 --- a/src/Helper/TypesExtractor.php +++ b/src/Helper/TypesExtractor.php @@ -13,27 +13,31 @@ use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; +use PHPStan\PhpDocParser\ParserConfig; final class TypesExtractor { private Lexer $lexer; + private PhpDocParser $phpDocParser; public function __construct() { - $this->lexer = new Lexer(); - $constExprParser = new ConstExprParser(); - $this->phpDocParser = new PhpDocParser(new TypeParser($constExprParser), $constExprParser); + $config = new ParserConfig([]); + $this->lexer = new Lexer($config); + $constExprParser = new ConstExprParser($config); + $this->phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser); } /** - * @return array + * @return array */ public function extractFromMethod(\ReflectionMethod $method): array { @@ -44,7 +48,7 @@ public function extractFromMethod(\ReflectionMethod $method): array $types[] = $this->getClassMembersTypes($methodType); } - return array_unique(array_merge(...($types ?: [[]]))); + return array_unique(array_merge(...$types)); } /** @@ -109,7 +113,7 @@ private function getPhpDocTypesNames(string $namespace, string $docComment, arra $phpDocNode = $this->phpDocParser->parse($tokens); foreach ($phpDocNode->getTags() as $tagNode) { - if (!in_array($tagNode->name, $tagNames, true)) { + if (!\in_array($tagNode->name, $tagNames, true)) { continue; } $results = array_merge($results, $this->extractFromTagNode($tagNode, $namespace)); @@ -124,7 +128,7 @@ private function getPhpDocTypesNames(string $namespace, string $docComment, arra private function extractFromTagNode(PhpDocTagNode $tagNode, string $namespace): array { $builtins = [ - 'int','string','bool','float','array','object','callable','iterable','mixed','void','null','false','true','self','static','parent' + 'int', 'string', 'bool', 'float', 'array', 'object', 'callable', 'iterable', 'mixed', 'void', 'null', 'false', 'true', 'self', 'static', 'parent', ]; $typeNode = $tagNode->value instanceof ReturnTagValueNode || $tagNode->value instanceof VarTagValueNode @@ -143,7 +147,7 @@ private function extractFromTagNode(PhpDocTagNode $tagNode, string $namespace): * @param array $builtins * @return array */ - private function collectTypeNames($typeNode, string $namespace, array $builtins): array + private function collectTypeNames(TypeNode $typeNode, string $namespace, array $builtins): array { $names = []; @@ -179,12 +183,14 @@ private function collectTypeNames($typeNode, string $namespace, array $builtins) */ private function resolveClassName(string $token, string $namespace, array $builtins): array { - $token = trim($token); - if ($token === '') { return []; } + $token = mb_trim($token); + if ($token === '') { + return []; + } $isFqcn = str_starts_with($token, '\\'); - $normalized = $isFqcn ? substr($token, 1) : $token; - if (in_array(strtolower($normalized), $builtins, true)) { + $normalized = $isFqcn ? mb_substr($token, 1) : $token; + if (\in_array(mb_strtolower($normalized), $builtins, true)) { return []; } @@ -200,8 +206,8 @@ private function resolveClassName(string $token, string $namespace, array $built } /** - * @param string[] $registeredTypes - * @return string[] + * @param class-string[] $registeredTypes + * @return class-string[] */ private function getClassMembersTypes(string $type, array $registeredTypes = []): array { @@ -236,6 +242,6 @@ private function getMembersTypes(array $members): array $types[] = $this->getTypes($member); } - return array_unique(array_merge(...($types ?: [[]]))); + return array_unique(array_merge(...$types)); } } diff --git a/src/Interceptor/Contract/Event/EventFactory.php b/src/Interceptor/Contract/Event/EventFactory.php index e61da77..973d55c 100644 --- a/src/Interceptor/Contract/Event/EventFactory.php +++ b/src/Interceptor/Contract/Event/EventFactory.php @@ -12,6 +12,7 @@ interface EventFactory { /** * @template T of Event + * @param Instance $instance * @param class-string $eventClassName * @return T */ diff --git a/src/Interceptor/Exception/InternalCodeRetrievalException.php b/src/Interceptor/Exception/InternalCodeRetrievalException.php index 0b67cec..19ce63d 100644 --- a/src/Interceptor/Exception/InternalCodeRetrievalException.php +++ b/src/Interceptor/Exception/InternalCodeRetrievalException.php @@ -9,7 +9,7 @@ final class InternalCodeRetrievalException extends \RuntimeException public function __construct(string $name) { parent::__construct( - sprintf( + \sprintf( 'Unable to retrieve code for method or class "%s".', $name, ) diff --git a/src/Interceptor/Impl/CacheInterceptor.php b/src/Interceptor/Impl/CacheInterceptor.php index fb432cf..7a8139c 100644 --- a/src/Interceptor/Impl/CacheInterceptor.php +++ b/src/Interceptor/Impl/CacheInterceptor.php @@ -16,7 +16,6 @@ use OpenClassrooms\ServiceProxy\Model\Request\Instance; use OpenClassrooms\ServiceProxy\Model\Response\Response; use OpenClassrooms\ServiceProxy\Util\Expression; -use Symfony\Component\PropertyInfo\Type; final class CacheInterceptor extends AbstractInterceptor implements SuffixInterceptor, PrefixInterceptor { @@ -43,7 +42,7 @@ final class CacheInterceptor extends AbstractInterceptor implements SuffixInterc public function __construct( ?CacheInterceptorConfig $config = null, - iterable $handlers = [], + iterable $handlers = [], ) { parent::__construct($handlers); @@ -67,6 +66,9 @@ public static function getMisses(?string $poolName = self::DEFAULT_POOL_NAME): a return self::$misses[$poolName] ?? []; } + /** + * @throws \ValueError + */ public function prefix(Instance $instance): Response { self::$hits = []; @@ -222,7 +224,7 @@ private function getTypesInnerCode(\ReflectionMethod $method): string private function getTypeInnerCode(string $type, string $code): string { - if (\in_array($type, Type::$builtinTypes, true)) { + if (\in_array($type, ['int', 'string', 'bool', 'float', 'array', 'object', 'callable', 'iterable', 'mixed', 'void', 'null', 'false', 'true', 'self', 'static', 'parent',], true)) { return $code . '.' . $type; } @@ -275,7 +277,7 @@ private function getInnerCode(\ReflectionMethod|\ReflectionClass $reflection): s $code = preg_replace('/\s+/', '', implode('', $code)); if ($code === null) { - throw new \RuntimeException(sprintf( + throw new \RuntimeException(\sprintf( 'An error occurred while cleaning %s %s\'s code.', $name, $reflection instanceof \ReflectionMethod ? 'method' : 'class', @@ -374,7 +376,7 @@ private function buildTag(AutoTaggable $object): string } /** - * @param \ReflectionClass $ref + * @param \ReflectionClass $ref */ private function getPropertyValue(\ReflectionClass $ref, object $object, string $propertyName): mixed { diff --git a/src/Interceptor/Impl/Event/ServiceProxyEventFactory.php b/src/Interceptor/Impl/Event/ServiceProxyEventFactory.php index 01671a2..4877cba 100644 --- a/src/Interceptor/Impl/Event/ServiceProxyEventFactory.php +++ b/src/Interceptor/Impl/Event/ServiceProxyEventFactory.php @@ -13,6 +13,7 @@ final class ServiceProxyEventFactory implements EventFactory { /** * @template T of Event + * @param Instance $instance * @param class-string $eventClassName * @return T */ diff --git a/src/Interceptor/Impl/EventInterceptor.php b/src/Interceptor/Impl/EventInterceptor.php index ec77686..a6ff374 100644 --- a/src/Interceptor/Impl/EventInterceptor.php +++ b/src/Interceptor/Impl/EventInterceptor.php @@ -6,6 +6,7 @@ use AutoMapper\AutoMapper; use AutoMapper\AutoMapperInterface; +use AutoMapper\Configuration; use OpenClassrooms\ServiceProxy\Attribute\Event; use OpenClassrooms\ServiceProxy\Handler\Contract\EventHandler; use OpenClassrooms\ServiceProxy\Interceptor\Config\EventInterceptorConfig; @@ -28,7 +29,10 @@ public function __construct( ) { parent::__construct($handlers); $tmpDir = $this->config->mapperCacheDir ?? sys_get_temp_dir() . '/mapper-cache'; - $this->mapper = AutoMapper::create(cacheDirectory: $tmpDir); + $this->mapper = AutoMapper::create( + new Configuration(), + $tmpDir + ); } public function getPrefixPriority(): int @@ -127,7 +131,7 @@ private function createMessage(string $messageClass, mixed $response): object { if (!\is_object($response) && !\is_array($response)) { throw new \InvalidArgumentException( - sprintf( + \sprintf( 'The response must be an object to guess arguments for message class "%s".', $messageClass ) diff --git a/src/Interceptor/Impl/InvalidateCacheInterceptor.php b/src/Interceptor/Impl/InvalidateCacheInterceptor.php index 3c1a2e1..2c4ef11 100644 --- a/src/Interceptor/Impl/InvalidateCacheInterceptor.php +++ b/src/Interceptor/Impl/InvalidateCacheInterceptor.php @@ -72,7 +72,7 @@ private function getTags(Instance $instance, InvalidateCache $attribute): array $guessedTags = array_values( array_filter( - $this->guessObjectsTags($instance->getMethod()->getResponse()) + $this->guessObjectsTags($instance->getMethod()->getResponse()), ) ); diff --git a/src/Interceptor/Impl/LockInterceptor.php b/src/Interceptor/Impl/LockInterceptor.php index ef68cc2..fe886c3 100644 --- a/src/Interceptor/Impl/LockInterceptor.php +++ b/src/Interceptor/Impl/LockInterceptor.php @@ -24,7 +24,7 @@ final class LockInterceptor extends AbstractInterceptor implements PrefixInterce * @param iterable $handlers */ public function __construct( - iterable $handlers = [], + iterable $handlers = [], ?LoggerInterface $logger = null, ) { $this->logger = $logger ?? new NullLogger(); @@ -41,6 +41,9 @@ public function getSuffixPriority(): int return 39; } + /** + * @param Instance $instance + */ public function prefix(Instance $instance): Response { $attribute = $instance->getMethod()->getAttribute(Lock::class); @@ -64,6 +67,9 @@ public function prefix(Instance $instance): Response return new Response(); } + /** + * @param Instance $instance + */ public function suffix(Instance $instance): Response { $key = $this->computeLockingKey($instance); @@ -99,6 +105,9 @@ public function supportsPrefix(Instance $instance): bool ; } + /** + * @param Instance $instance + */ private function computeLockingKey(Instance $instance): string { $key = $instance->getMethod()->getAttribute(Lock::class)->key; diff --git a/src/Interceptor/Impl/SecurityInterceptor.php b/src/Interceptor/Impl/SecurityInterceptor.php index 37994e2..e677567 100644 --- a/src/Interceptor/Impl/SecurityInterceptor.php +++ b/src/Interceptor/Impl/SecurityInterceptor.php @@ -20,7 +20,7 @@ final class SecurityInterceptor extends AbstractInterceptor implements PrefixInt private readonly LoggerInterface $logger; public function __construct( - iterable $handlers = [], + iterable $handlers = [], private readonly ?SecurityInterceptorConfig $config = null, ?LoggerInterface $logger = null, ) { @@ -128,14 +128,14 @@ private function camelCaseToSnakeCase(string $string): string */ private function resolveExpression( SecurityHandler $handler, - string $expression, - array $parameters, - Security $attribute, + string $expression, + array $parameters, + Security $attribute, ): void { $expressionLanguage = new ExpressionLanguage(); $expressionLanguage->register( 'is_granted', - static fn ($attributes, string $object = 'null') => sprintf( + static fn ($attributes, string $object = 'null') => \sprintf( 'return $handler->checkAccess(%s, %s)', $attributes, $object @@ -151,7 +151,7 @@ private function resolveExpression( if (!$authorized) { if ($attribute->exception !== null) { $exception = $attribute->exception; - throw new $exception($attribute->message); + throw new $exception($attribute->message ?? 'Access denied.'); } throw $handler->getAccessDeniedException($attribute->message); diff --git a/src/Interceptor/Impl/TransactionInterceptor.php b/src/Interceptor/Impl/TransactionInterceptor.php index 5c3c208..726392f 100644 --- a/src/Interceptor/Impl/TransactionInterceptor.php +++ b/src/Interceptor/Impl/TransactionInterceptor.php @@ -81,10 +81,6 @@ public function getSuffixPriority(): int } /** - * @template T of object - * - * @param Instance $instance - * * @throws \Exception */ private function handleMappedException(\Exception $thrownException, Transaction $attribute): void diff --git a/src/Model/Event.php b/src/Model/Event.php index c8bf319..46076d4 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -25,20 +25,9 @@ public function __construct( public readonly mixed $exception = null, public readonly Moment $type = Moment::SUFFIX ) { - $this->name = self::getName( - className: $class, - moment: $type, - transport: Transport::SYNC, - method: $method, - name: $name - ); + $this->name = self::getName($class, $type, Transport::SYNC, $method, $name); } - /** - * @template T of object - * - * @param Instance $instance - */ public function getUseCaseRequest(): mixed { return $this->parameters['useCaseRequest'] ?? ($this->parameters['request'] ?? null); diff --git a/src/Model/Message.php b/src/Model/Message.php index 55df99f..8f47998 100644 --- a/src/Model/Message.php +++ b/src/Model/Message.php @@ -9,7 +9,7 @@ final class Message { /** - * @param array $headers + * @param array $headers */ public function __construct( public readonly MessageContext $context, diff --git a/src/Model/Request/Method.php b/src/Model/Request/Method.php index 63cad01..7390e35 100644 --- a/src/Model/Request/Method.php +++ b/src/Model/Request/Method.php @@ -67,7 +67,7 @@ public function getAnnotation(string $annotationClass): object } /** - * @template T + * @template T of object * * @param class-string|null $annotationClass * diff --git a/src/ProxyFactory.php b/src/ProxyFactory.php index a76de2a..7349246 100644 --- a/src/ProxyFactory.php +++ b/src/ProxyFactory.php @@ -36,9 +36,9 @@ final class ProxyFactory */ public function __construct( ProxyFactoryConfiguration $configuration, - iterable $prefixInterceptors, - iterable $suffixInterceptors, - Reader|null $annotationReader = null, + iterable $prefixInterceptors, + iterable $suffixInterceptors, + Reader|null $annotationReader = null, ) { $this->configuration = $configuration; $this->interceptors = [ @@ -68,7 +68,7 @@ public function createInstance(string $class, ...$args): object $instanceRef = new \ReflectionClass($class); if ($instanceRef->isFinal()) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'Unable to proxify final class %s. Hint: replace final keywords with a @final annotation', $instanceRef->getName() )); @@ -94,7 +94,7 @@ public function createInstance(string $class, ...$args): object $interceptors = $this->filterInterceptors($instance, $type); if (\count($interceptors) > 0) { if ($methodRef->isFinal()) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'Unable to proxify a final method %s on class %s. Hint: replace final keywords with a @final annotation', $methodRef->getName(), $methodRef->getDeclaringClass() @@ -102,7 +102,7 @@ public function createInstance(string $class, ...$args): object } if ($methodRef->isPrivate()) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'Unable to attach an interceptor to a private method %s on class %s.', $methodRef->getName(), $methodRef->getDeclaringClass() @@ -148,11 +148,11 @@ public function getInterceptors(): array * @param Instance $instance */ private function intercept( - string $type, - array $interceptors, + string $type, + array $interceptors, Instance $instance, - mixed $response, - bool &$returnEarly + mixed $response, + bool &$returnEarly ): mixed { foreach ($interceptors as $interceptor) { if ($type === PrefixInterceptor::PREFIX_TYPE && $interceptor instanceof PrefixInterceptor) { From 1541d654d885bd2a63d84d0ba52926192ebe7752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 12:35:44 +0100 Subject: [PATCH 12/15] Fix cs --- .../Symfony/CacheWarmer/ProxyCacheWarmer.php | 2 -- src/Interceptor/Impl/CacheInterceptor.php | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php index 9c48335..bb67d27 100644 --- a/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php +++ b/src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php @@ -25,8 +25,6 @@ public function __construct(iterable $proxies) /** * {@inheritdoc} - * @param string $cacheDir - * @param string|null $buildDir */ public function warmUp(string $cacheDir, ?string $buildDir = null): array { diff --git a/src/Interceptor/Impl/CacheInterceptor.php b/src/Interceptor/Impl/CacheInterceptor.php index 7a8139c..e8bc2bd 100644 --- a/src/Interceptor/Impl/CacheInterceptor.php +++ b/src/Interceptor/Impl/CacheInterceptor.php @@ -224,7 +224,28 @@ private function getTypesInnerCode(\ReflectionMethod $method): string private function getTypeInnerCode(string $type, string $code): string { - if (\in_array($type, ['int', 'string', 'bool', 'float', 'array', 'object', 'callable', 'iterable', 'mixed', 'void', 'null', 'false', 'true', 'self', 'static', 'parent',], true)) { + if (\in_array( + $type, + [ + 'int', + 'string', + 'bool', + 'float', + 'array', + 'object', + 'callable', + 'iterable', + 'mixed', + 'void', + 'null', + 'false', + 'true', + 'self', + 'static', + 'parent', + ], + true + )) { return $code . '.' . $type; } From ac05b5853a8f412dd2e0fd3865e33129d0e62fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 12:40:10 +0100 Subject: [PATCH 13/15] Fix symfony 6.4 typing --- .../Messenger/Transport/Serialization/MessageSerializer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php b/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php index 46865a3..43b5c4a 100644 --- a/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php +++ b/src/FrameworkBridge/Symfony/Messenger/Transport/Serialization/MessageSerializer.php @@ -15,6 +15,7 @@ final class MessageSerializer extends Serializer */ public function encode(Envelope $envelope): array { + /** @var array{body: string, headers?: array} $result */ $result = parent::encode($envelope); $message = $envelope->getMessage(); if ($message instanceof Message) { From 9ea524c6d13ad684496421931b94212036b02303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 12:43:53 +0100 Subject: [PATCH 14/15] Fix php 8.5 typing --- src/Interceptor/Impl/CacheInterceptor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Interceptor/Impl/CacheInterceptor.php b/src/Interceptor/Impl/CacheInterceptor.php index e8bc2bd..79cdbfa 100644 --- a/src/Interceptor/Impl/CacheInterceptor.php +++ b/src/Interceptor/Impl/CacheInterceptor.php @@ -53,7 +53,7 @@ public function __construct( /** * @return array */ - public static function getHits(?string $poolName = self::DEFAULT_POOL_NAME): array + public static function getHits(string $poolName = self::DEFAULT_POOL_NAME): array { return self::$hits[$poolName] ?? []; } @@ -61,7 +61,7 @@ public static function getHits(?string $poolName = self::DEFAULT_POOL_NAME): arr /** * @return array */ - public static function getMisses(?string $poolName = self::DEFAULT_POOL_NAME): array + public static function getMisses(string $poolName = self::DEFAULT_POOL_NAME): array { return self::$misses[$poolName] ?? []; } From 6afd6eb5662acbd961d3e4643adab9d019f5d7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Letord?= Date: Thu, 11 Dec 2025 15:06:29 +0100 Subject: [PATCH 15/15] Fix php 8.5 typing --- src/Interceptor/Impl/CacheInterceptor.php | 2 -- src/Interceptor/Impl/InvalidateCacheInterceptor.php | 1 - 2 files changed, 3 deletions(-) diff --git a/src/Interceptor/Impl/CacheInterceptor.php b/src/Interceptor/Impl/CacheInterceptor.php index 79cdbfa..69518f4 100644 --- a/src/Interceptor/Impl/CacheInterceptor.php +++ b/src/Interceptor/Impl/CacheInterceptor.php @@ -412,8 +412,6 @@ private function getPropertyValue(\ReflectionClass $ref, object $object, string return null; } - $propRef->setAccessible(true); - return $propRef->getValue($object); } diff --git a/src/Interceptor/Impl/InvalidateCacheInterceptor.php b/src/Interceptor/Impl/InvalidateCacheInterceptor.php index 2c4ef11..096fc2e 100644 --- a/src/Interceptor/Impl/InvalidateCacheInterceptor.php +++ b/src/Interceptor/Impl/InvalidateCacheInterceptor.php @@ -124,7 +124,6 @@ private function getPropertyValue(\ReflectionClass $ref, object $object, string if (!$propRef->isInitialized($object)) { return false; } - $propRef->setAccessible(true); return $propRef->getValue($object); }