diff --git a/src/Configuration/Cache.php b/src/Configuration/Cache.php index 19d880ab..5e23b1e8 100644 --- a/src/Configuration/Cache.php +++ b/src/Configuration/Cache.php @@ -17,6 +17,7 @@ * @author Fabien Potencier * @Annotation */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Cache extends ConfigurationAnnotation { /** @@ -30,7 +31,7 @@ class Cache extends ConfigurationAnnotation * The number of seconds that the response is considered fresh by a private * cache like a web browser. * - * @var int + * @var int|string|null */ private $maxage; @@ -38,7 +39,7 @@ class Cache extends ConfigurationAnnotation * The number of seconds that the response is considered fresh by a public * cache like a reverse proxy cache. * - * @var int + * @var int|string|null */ private $smaxage; @@ -101,6 +102,42 @@ class Cache extends ConfigurationAnnotation */ private $staleIfError; + /** + * @param int|string|null $maxage + * @param int|string|null $smaxage + * @param int|string|null $maxstale + * @param int|string|null $staleWhileRevalidate + * @param int|string|null $staleIfError + */ + public function __construct( + array $values = [], + string $expires = null, + $maxage = null, + $smaxage = null, + bool $public = false, + bool $mustRevalidate = false, + array $vary = [], + ?string $lastModified = null, + ?string $etag = null, + $maxstale = null, + $staleWhileRevalidate = null, + $staleIfError = null + ) { + $values['expires'] = $values['expires'] ?? $expires; + $values['maxage'] = $values['maxage'] ?? $maxage; + $values['smaxage'] = $values['smaxage'] ?? $smaxage; + $values['public'] = $values['public'] ?? $public; + $values['mustRevalidate'] = $values['mustRevalidate'] ?? $mustRevalidate; + $values['vary'] = $values['vary'] ?? $vary; + $values['lastModified'] = $values['lastModified'] ?? $lastModified; + $values['Etag'] = $values['Etag'] ?? $etag; + $values['maxstale'] = $values['maxstale'] ?? $maxstale; + $values['staleWhileRevalidate'] = $values['staleWhileRevalidate'] ?? $staleWhileRevalidate; + $values['staleIfError'] = $values['staleIfError'] ?? $staleIfError; + + parent::__construct($values); + } + /** * Returns the expiration date for the Expires header field. * diff --git a/src/Configuration/Entity.php b/src/Configuration/Entity.php index fe3908df..1273d38d 100644 --- a/src/Configuration/Entity.php +++ b/src/Configuration/Entity.php @@ -17,6 +17,7 @@ * @author Ryan Weaver * @Annotation */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Entity extends ParamConverter { public function setExpr($expr) @@ -26,4 +27,29 @@ public function setExpr($expr) $this->setOptions($options); } + + /** + * @param array|string $data + */ + public function __construct( + $data = [], + string $expr = null, + string $class = null, + array $options = [], + bool $isOptional = false, + string $converter = null + ) { + $values = []; + if (\is_string($data)) { + $values['value'] = $data; + } else { + $values = $data; + } + + $values['expr'] = $values['expr'] ?? $expr; + + parent::__construct($values, $class, $options, $isOptional, $converter); + + $this->setExpr($values['expr']); + } } diff --git a/src/Configuration/IsGranted.php b/src/Configuration/IsGranted.php index 80b084db..f9f7f87d 100644 --- a/src/Configuration/IsGranted.php +++ b/src/Configuration/IsGranted.php @@ -17,6 +17,7 @@ * @author Ryan Weaver * @Annotation */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class IsGranted extends ConfigurationAnnotation { /** @@ -50,6 +51,29 @@ class IsGranted extends ConfigurationAnnotation */ private $statusCode; + /** + * @param mixed $subject + * @param array|string $data + */ + public function __construct( + $data = [], + $subject = null, + string $message = null, + ?int $statusCode = null + ) { + $values = []; + if (\is_string($data)) { + $values['attributes'] = $data; + } else { + $values = $data; + } + + $values['subject'] = $values['subject'] ?? $subject; + $values['message'] = $values['message'] ?? $message; + $values['statusCode'] = $values['statusCode'] ?? $statusCode; + parent::__construct($values); + } + public function setAttributes($attributes) { $this->attributes = $attributes; diff --git a/src/Configuration/ParamConverter.php b/src/Configuration/ParamConverter.php index d9d51c8e..964044d0 100644 --- a/src/Configuration/ParamConverter.php +++ b/src/Configuration/ParamConverter.php @@ -17,6 +17,7 @@ * @author Fabien Potencier * @Annotation */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class ParamConverter extends ConfigurationAnnotation { /** @@ -54,6 +55,29 @@ class ParamConverter extends ConfigurationAnnotation */ private $converter; + /** + * @param array|string $data + */ + public function __construct( + $data = [], + string $class = null, + array $options = [], + bool $isOptional = false, + string $converter = null + ) { + $values = []; + if (\is_string($data)) { + $values['value'] = $data; + } else { + $values = $data; + } + $values['class'] = $values['class'] ?? $class; + $values['options'] = $values['options'] ?? $options; + $values['isOptional'] = $values['isOptional'] ?? $isOptional; + $values['converter'] = $values['converter'] ?? $converter; + parent::__construct($values); + } + /** * Returns the parameter name. * diff --git a/src/Configuration/Security.php b/src/Configuration/Security.php index b0e72a4b..c884eb63 100644 --- a/src/Configuration/Security.php +++ b/src/Configuration/Security.php @@ -17,6 +17,7 @@ * @author Fabien Potencier * @Annotation */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Security extends ConfigurationAnnotation { /** @@ -43,6 +44,27 @@ class Security extends ConfigurationAnnotation */ protected $message = 'Access denied.'; + /** + * @param array|string $data + */ + public function __construct( + $data = [], + string $message = null, + ?int $statusCode = null + ) { + $values = []; + if (\is_string($data)) { + $values['expression'] = $data; + } else { + $values = $data; + } + + $values['message'] = $values['message'] ?? $message; + $values['statusCode'] = $values['statusCode'] ?? $statusCode; + + parent::__construct($values); + } + public function getExpression() { return $this->expression; diff --git a/src/Configuration/Template.php b/src/Configuration/Template.php index 6d727d6c..98bbbd6f 100644 --- a/src/Configuration/Template.php +++ b/src/Configuration/Template.php @@ -17,6 +17,7 @@ * @author Fabien Potencier * @Annotation */ +#[\Attribute(\Attribute::TARGET_METHOD)] class Template extends ConfigurationAnnotation { /** @@ -47,6 +48,29 @@ class Template extends ConfigurationAnnotation */ private $owner = []; + /** + * @param array|string $data + */ + public function __construct( + $data = [], + array $vars = [], + bool $isStreamable = false, + array $owner = [] + ) { + $values = []; + if (\is_string($data)) { + $values['template'] = $data; + } else { + $values = $data; + } + + $values['isStreamable'] = $values['isStreamable'] ?? $isStreamable; + $values['vars'] = $values['vars'] ?? $vars; + $values['owner'] = $values['owner'] ?? $owner; + + parent::__construct($values); + } + /** * Returns the array of templates variables. * diff --git a/src/EventListener/ControllerListener.php b/src/EventListener/ControllerListener.php index 97b07bfe..b0897f60 100644 --- a/src/EventListener/ControllerListener.php +++ b/src/EventListener/ControllerListener.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\Reader; use Doctrine\Persistence\Proxy; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\KernelEvent; @@ -60,6 +61,24 @@ public function onKernelController(KernelEvent $event) $classConfigurations = $this->getConfigurations($this->reader->getClassAnnotations($object)); $methodConfigurations = $this->getConfigurations($this->reader->getMethodAnnotations($method)); + if (80000 <= \PHP_VERSION_ID) { + $classAttributes = array_map( + function (\ReflectionAttribute $attribute) { + return $attribute->newInstance(); + }, + $object->getAttributes(ConfigurationAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF) + ); + $classConfigurations = array_merge($classConfigurations, $this->getConfigurations($classAttributes)); + + $methodAttributes = array_map( + function (\ReflectionAttribute $attribute) { + return $attribute->newInstance(); + }, + $method->getAttributes(ConfigurationAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF) + ); + $methodConfigurations = array_merge($methodConfigurations, $this->getConfigurations($methodAttributes)); + } + $configurations = []; foreach (array_merge(array_keys($classConfigurations), array_keys($methodConfigurations)) as $key) { if (!\array_key_exists($key, $classConfigurations)) { diff --git a/tests/EventListener/ControllerListenerTest.php b/tests/EventListener/ControllerListenerTest.php index 3acf41d9..09d881a2 100644 --- a/tests/EventListener/ControllerListenerTest.php +++ b/tests/EventListener/ControllerListenerTest.php @@ -13,13 +13,34 @@ use Doctrine\Common\Annotations\AnnotationReader; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtClass; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtClassAndMethod; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAttributeAtClass; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAttributeAtClassAndMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAttributeAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerEntityAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerEntityAttributeAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerIsGrantedAtClass; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerIsGrantedAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerIsGrantedAttributeAtClass; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerIsGrantedAttributeAtMethod; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerMultipleCacheAtClass; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerMultipleCacheAtMethod; use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerParamConverterAtClassAndMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerParamConverterAttributeAtClassAndMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerSecurityAtClass; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerSecurityAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerSecurityAttributeAtClass; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerSecurityAttributeAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerTemplateAtMethod; +use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerTemplateAttributeAtMethod; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -32,7 +53,7 @@ protected function setUp(): void $this->request = $this->createRequest(); // trigger the autoloading of the @Cache annotation - class_exists('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache'); + class_exists(Cache::class); } protected function tearDown(): void @@ -52,6 +73,20 @@ public function testCacheAnnotationAtMethod() $this->assertEquals(FooControllerCacheAtMethod::METHOD_SMAXAGE, $this->getReadedCache()->getSMaxAge()); } + /** + * @requires PHP 8.0 + */ + public function testCacheAttributeAtMethod() + { + $controller = new FooControllerCacheAttributeAtMethod(); + + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $this->assertNotNull($this->getReadedCache()); + $this->assertEquals(FooControllerCacheAtMethod::METHOD_SMAXAGE, $this->getReadedCache()->getSMaxAge()); + } + public function testCacheAnnotationAtClass() { $controller = new FooControllerCacheAtClass(); @@ -62,6 +97,19 @@ public function testCacheAnnotationAtClass() $this->assertEquals(FooControllerCacheAtClass::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge()); } + /** + * @requires PHP 8.0 + */ + public function testCacheAttributeAtClass() + { + $controller = new FooControllerCacheAttributeAtClass(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $this->assertNotNull($this->getReadedCache()); + $this->assertEquals(FooControllerCacheAtClass::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge()); + } + public function testCacheAnnotationAtClassAndMethod() { $controller = new FooControllerCacheAtClassAndMethod(); @@ -78,6 +126,25 @@ public function testCacheAnnotationAtClassAndMethod() $this->assertEquals(FooControllerCacheAtClassAndMethod::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge()); } + /** + * @requires PHP 8.0 + */ + public function testCacheAttributeAtClassAndMethod() + { + $controller = new FooControllerCacheAttributeAtClassAndMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $this->assertNotNull($this->getReadedCache()); + $this->assertEquals(FooControllerCacheAttributeAtClassAndMethod::METHOD_SMAXAGE, $this->getReadedCache()->getSMaxAge()); + + $this->event = $this->getFilterControllerEvent([$controller, 'bar2Action'], $this->request); + $this->listener->onKernelController($this->event); + + $this->assertNotNull($this->getReadedCache()); + $this->assertEquals(FooControllerCacheAttributeAtClassAndMethod::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge()); + } + public function testMultipleAnnotationsOnClassThrowsExceptionUnlessConfigurationAllowsArray() { $this->expectException(\LogicException::class); @@ -100,7 +167,7 @@ public function testMultipleAnnotationsOnMethodThrowsExceptionUnlessConfiguratio public function testMultipleParamConverterAnnotationsOnMethod() { - $paramConverter = new \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter([]); + $paramConverter = new ParamConverter([]); $controller = new FooControllerParamConverterAtClassAndMethod(); $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); $this->listener->onKernelController($this->event); @@ -108,16 +175,218 @@ public function testMultipleParamConverterAnnotationsOnMethod() $annotations = $this->request->attributes->get('_converters'); $this->assertNotNull($annotations); $this->assertArrayHasKey(0, $annotations); - $this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter', $annotations[0]); + $this->assertInstanceOf(ParamConverter::class, $annotations[0]); $this->assertEquals('test', $annotations[0]->getName()); $this->assertArrayHasKey(1, $annotations); - $this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter', $annotations[1]); + $this->assertInstanceOf(ParamConverter::class, $annotations[1]); $this->assertEquals('test2', $annotations[1]->getName()); $this->assertCount(2, $annotations); } + /** + * @requires PHP 8.0 + */ + public function testMultipleParamConverterAttributesOnMethod() + { + $paramConverter = new ParamConverter([]); + $controller = new FooControllerParamConverterAttributeAtClassAndMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_converters'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(ParamConverter::class, $annotations[0]); + $this->assertEquals('test', $annotations[0]->getName()); + + $this->assertArrayHasKey(1, $annotations); + $this->assertInstanceOf(ParamConverter::class, $annotations[1]); + $this->assertEquals('test2', $annotations[1]->getName()); + + $this->assertCount(2, $annotations); + } + + public function testEntityAnnotationOnMethod() + { + $controller = new FooControllerEntityAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_converters'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Entity::class, $annotations[0]); + $this->assertEquals('foo', $annotations[0]->getName()); + } + + /** + * @requires PHP 8.0 + */ + public function testEntityAttributeOnMethod() + { + $controller = new FooControllerEntityAttributeAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_converters'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Entity::class, $annotations[0]); + $this->assertEquals('foo', $annotations[0]->getName()); + } + + public function testIsGrantedAnnotationOnClass() + { + $controller = new FooControllerIsGrantedAtClass(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_is_granted'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(IsGranted::class, $annotations[0]); + $this->assertEquals('ROLE_USER', $annotations[0]->getAttributes()); + } + + /** + * @requires PHP 8.0 + */ + public function testIsGrantedAttributeOnClass() + { + $controller = new FooControllerIsGrantedAttributeAtClass(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_is_granted'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(IsGranted::class, $annotations[0]); + $this->assertEquals('ROLE_USER', $annotations[0]->getAttributes()); + } + + public function testIsGrantedAnnotationOnMethod() + { + $controller = new FooControllerIsGrantedAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_is_granted'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(1, $annotations); + $this->assertInstanceOf(IsGranted::class, $annotations[0]); + $this->assertEquals('ROLE_USER', $annotations[0]->getAttributes()); + $this->assertInstanceOf(IsGranted::class, $annotations[1]); + $this->assertEquals('FOO_SHOW', $annotations[1]->getAttributes()); + $this->assertEquals('foo', $annotations[1]->getSubject()); + } + + /** + * @requires PHP 8.0 + */ + public function testIsGrantedAttributeOnMethod() + { + $controller = new FooControllerIsGrantedAttributeAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_is_granted'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(1, $annotations); + $this->assertInstanceOf(IsGranted::class, $annotations[0]); + $this->assertEquals('ROLE_USER', $annotations[0]->getAttributes()); + $this->assertInstanceOf(IsGranted::class, $annotations[1]); + $this->assertEquals('FOO_SHOW', $annotations[1]->getAttributes()); + $this->assertEquals('foo', $annotations[1]->getSubject()); + } + + public function testSecurityAnnotationOnClass() + { + $controller = new FooControllerSecurityAtClass(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_security'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Security::class, $annotations[0]); + $this->assertEquals("is_granted('ROLE_USER')", $annotations[0]->getExpression()); + } + + /** + * @requires PHP 8.0 + */ + public function testSecurityAttributeOnClass() + { + $controller = new FooControllerSecurityAttributeAtClass(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_security'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Security::class, $annotations[0]); + $this->assertEquals("is_granted('ROLE_USER')", $annotations[0]->getExpression()); + } + + public function testSecurityAnnotationOnMethod() + { + $controller = new FooControllerSecurityAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_security'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Security::class, $annotations[0]); + $this->assertEquals("is_granted('ROLE_USER') and is_granted('FOO_SHOW', foo)", $annotations[0]->getExpression()); + } + + /** + * @requires PHP 8.0 + */ + public function testSecurityAttributeOnMethod() + { + $controller = new FooControllerSecurityAttributeAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotations = $this->request->attributes->get('_security'); + $this->assertNotNull($annotations); + $this->assertArrayHasKey(0, $annotations); + $this->assertInstanceOf(Security::class, $annotations[0]); + $this->assertEquals("is_granted('ROLE_USER') and is_granted('FOO_SHOW', foo)", $annotations[0]->getExpression()); + } + + public function testTemplateAnnotationOnMethod() + { + $controller = new FooControllerTemplateAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + $annotation = $this->request->attributes->get('_template'); + $this->assertNotNull($annotation); + $this->assertInstanceOf(Template::class, $annotation); + $this->assertEquals('templates/bar.html.twig', $annotation->getTemplate()); + $this->assertEquals(['foo'], $annotation->getVars()); + } + + /** + * @requires PHP 8.0 + */ + public function testTemplateAttributeOnMethod() + { + $controller = new FooControllerTemplateAttributeAtMethod(); + $this->event = $this->getFilterControllerEvent([$controller, 'barAction'], $this->request); + $this->listener->onKernelController($this->event); + + $annotation = $this->request->attributes->get('_template'); + $this->assertNotNull($annotation); + $this->assertInstanceOf(Template::class, $annotation); + $this->assertEquals('templates/bar.html.twig', $annotation->getTemplate()); + $this->assertEquals(['foo'], $annotation->getVars()); + } + private function createRequest(Cache $cache = null) { return new Request([], [], [ @@ -127,7 +396,7 @@ private function createRequest(Cache $cache = null) private function getFilterControllerEvent($controller, Request $request) { - $mockKernel = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\Kernel', ['', '']); + $mockKernel = $this->getMockForAbstractClass(\Symfony\Component\HttpKernel\Kernel::class, ['', '']); return new ControllerEvent($mockKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); } diff --git a/tests/EventListener/Fixture/FooControllerCacheAttributeAtClass.php b/tests/EventListener/Fixture/FooControllerCacheAttributeAtClass.php new file mode 100644 index 00000000..0e70bd04 --- /dev/null +++ b/tests/EventListener/Fixture/FooControllerCacheAttributeAtClass.php @@ -0,0 +1,15 @@ +assertEquals('5', $this->response->headers->getCacheControlDirective('max-stale')); $this->assertEquals('6', $this->response->headers->getCacheControlDirective('stale-while-revalidate')); $this->assertEquals('7', $this->response->headers->getCacheControlDirective('stale-if-error')); - $this->assertInstanceOf('DateTime', $this->response->getExpires()); + $this->assertInstanceOf(\DateTime::class, $this->response->getExpires()); } public function testCacheMaxAgeSupportsStrtotimeFormat() @@ -194,7 +194,7 @@ public function testLastModifiedHeader() public function testEtagNotModifiedResponse() { - $request = $this->createRequest(new Cache(['etag' => 'test.getId()'])); + $request = $this->createRequest(new Cache(['Etag' => 'test.getId()'])); $request->attributes->set('test', $entity = new TestEntity()); $request->headers->add(['If-None-Match' => sprintf('"%s"', hash('sha256', $entity->getId()))]); @@ -211,7 +211,7 @@ public function testEtagNotModifiedResponse() public function testEtagHeader() { - $request = $this->createRequest(new Cache(['ETag' => 'test.getId()'])); + $request = $this->createRequest(new Cache(['Etag' => 'test.getId()'])); $request->attributes->set('test', $entity = new TestEntity()); $response = new Response(); @@ -276,7 +276,7 @@ private function createEventMock(Request $request, Response $response) private function getKernel() { - return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + return $this->getMockBuilder(HttpKernelInterface::class)->getMock(); } } diff --git a/tests/EventListener/IsGrantedListenerTest.php b/tests/EventListener/IsGrantedListenerTest.php index edce51e0..f0ff10b9 100644 --- a/tests/EventListener/IsGrantedListenerTest.php +++ b/tests/EventListener/IsGrantedListenerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -153,7 +154,7 @@ public function getAccessDeniedMessageTests() public function testNotFoundHttpException() { - $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class); + $this->expectException(HttpException::class); $this->expectExceptionMessage('Not found'); $request = $this->createRequest(new IsGranted(['attributes' => 'ROLE_ADMIN', 'statusCode' => 404, 'message' => 'Not found'])); diff --git a/tests/EventListener/SecurityListenerTest.php b/tests/EventListener/SecurityListenerTest.php index 91beba46..9123e2e4 100644 --- a/tests/EventListener/SecurityListenerTest.php +++ b/tests/EventListener/SecurityListenerTest.php @@ -18,15 +18,23 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Role\RoleHierarchy; class SecurityListenerTest extends \PHPUnit\Framework\TestCase { public function testAccessDenied() { - $this->expectException(\Symfony\Component\Security\Core\Exception\AccessDeniedException::class); + $this->expectException(AccessDeniedException::class); $request = $this->createRequest(new Security(['expression' => 'is_granted("ROLE_ADMIN") or is_granted("FOO")'])); - $event = new ControllerArgumentsEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), function () { + $event = new ControllerArgumentsEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), function () { return new Response(); }, [], $request, null); @@ -35,11 +43,11 @@ public function testAccessDenied() public function testNotFoundHttpException() { - $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class); + $this->expectException(HttpException::class); $this->expectExceptionMessage('Not found'); $request = $this->createRequest(new Security(['expression' => 'is_granted("ROLE_ADMIN") or is_granted("FOO")', 'statusCode' => 404, 'message' => 'Not found'])); - $event = new ControllerArgumentsEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), function () { + $event = new ControllerArgumentsEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), function () { return new Response(); }, [], $request, null); @@ -48,19 +56,19 @@ public function testNotFoundHttpException() private function getListener() { - $roleHierarchy = $this->getMockBuilder('Symfony\Component\Security\Core\Role\RoleHierarchy')->disableOriginalConstructor()->getMock(); + $roleHierarchy = $this->getMockBuilder(RoleHierarchy::class)->disableOriginalConstructor()->getMock(); $roleHierarchy->expects($this->once())->method('getReachableRoleNames')->willReturn([]); - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\AbstractToken')->getMock(); + $token = $this->getMockBuilder(AbstractToken::class)->getMock(); $token->expects($this->once())->method('getRoleNames')->willReturn([]); - $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); $tokenStorage->expects($this->exactly(2))->method('getToken')->willReturn($token); - $authChecker = $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')->getMock(); + $authChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); $authChecker->expects($this->exactly(2))->method('isGranted')->willReturn(false); - $trustResolver = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface')->getMock(); + $trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock(); $argNameConverter = $this->createArgumentNameConverter([]);