diff --git a/Neos.Flow/Classes/Annotations/After.php b/Neos.Flow/Classes/Annotations/After.php index 42643ebc55..e2550e8ef8 100644 --- a/Neos.Flow/Classes/Annotations/After.php +++ b/Neos.Flow/Classes/Annotations/After.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a method as an after advice to be triggered after any * pointcut matching the given expression. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class After { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('An After annotation must specify a pointcut expression.', 1318456620); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; + $this->pointcutExpression = $pointcutExpression; } } diff --git a/Neos.Flow/Classes/Annotations/AfterReturning.php b/Neos.Flow/Classes/Annotations/AfterReturning.php index 73217f5772..ef7ba527a9 100644 --- a/Neos.Flow/Classes/Annotations/AfterReturning.php +++ b/Neos.Flow/Classes/Annotations/AfterReturning.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a method as an after returning advice to be triggered * after any pointcut matching the given expression returns. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class AfterReturning { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('An AfterReturning annotation must specify a pointcut expression.', 1318456618); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; + $this->pointcutExpression = $pointcutExpression; } } diff --git a/Neos.Flow/Classes/Annotations/AfterThrowing.php b/Neos.Flow/Classes/Annotations/AfterThrowing.php index 53e7375c7a..5cebadd2cd 100644 --- a/Neos.Flow/Classes/Annotations/AfterThrowing.php +++ b/Neos.Flow/Classes/Annotations/AfterThrowing.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a method as an after throwing advice to be triggered * after any pointcut matching the given expression throws an exception. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class AfterThrowing { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('An AfterThrowing annotation must specify a pointcut expression.', 1318456616); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; + $this->pointcutExpression = $pointcutExpression; } } diff --git a/Neos.Flow/Classes/Annotations/Around.php b/Neos.Flow/Classes/Annotations/Around.php index 471bd4b12d..fc1c4b7ed2 100644 --- a/Neos.Flow/Classes/Annotations/Around.php +++ b/Neos.Flow/Classes/Annotations/Around.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a method as an around advice to be triggered around any * pointcut matching the given expression. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Around { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('An Around annotation must specify a pointcut expression.', 1318456614); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; + $this->pointcutExpression = $pointcutExpression; } } diff --git a/Neos.Flow/Classes/Annotations/Aspect.php b/Neos.Flow/Classes/Annotations/Aspect.php index d136c8d681..94581f32f0 100644 --- a/Neos.Flow/Classes/Annotations/Aspect.php +++ b/Neos.Flow/Classes/Annotations/Aspect.php @@ -20,6 +20,7 @@ * @Annotation * @Target("CLASS") */ +#[\Attribute(\Attribute::TARGET_CLASS)] final class Aspect { } diff --git a/Neos.Flow/Classes/Annotations/Autowiring.php b/Neos.Flow/Classes/Annotations/Autowiring.php index 2e720cb117..80349036a8 100644 --- a/Neos.Flow/Classes/Annotations/Autowiring.php +++ b/Neos.Flow/Classes/Annotations/Autowiring.php @@ -11,13 +11,17 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to disable autowiring for Dependency Injection on the * whole class or on the annotated property only. * * @Annotation + * @NamedArgumentConstructor * @Target({"METHOD", "CLASS"}) */ +#[\Attribute(\Attribute::TARGET_METHOD|\Attribute::TARGET_CLASS)] final class Autowiring { /** @@ -26,15 +30,8 @@ final class Autowiring */ public $enabled = true; - /** - * @param array $values - */ - public function __construct(array $values) + public function __construct(bool $enabled = true) { - if (isset($values['enabled'])) { - $this->enabled = (boolean)$values['enabled']; - } elseif (isset($values['value'])) { - $this->enabled = (boolean)$values['value']; - } + $this->enabled = $enabled; } } diff --git a/Neos.Flow/Classes/Annotations/Before.php b/Neos.Flow/Classes/Annotations/Before.php index 7d366a99c0..0a3881e7da 100644 --- a/Neos.Flow/Classes/Annotations/Before.php +++ b/Neos.Flow/Classes/Annotations/Before.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a method as an before advice to be triggered before any * pointcut matching the given expression. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Before { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('A Before annotation must specify a pointcut expression.', 1318456622); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; + $this->pointcutExpression = $pointcutExpression; } } diff --git a/Neos.Flow/Classes/Annotations/CompileStatic.php b/Neos.Flow/Classes/Annotations/CompileStatic.php index 5bff108f2d..80cbc76737 100644 --- a/Neos.Flow/Classes/Annotations/CompileStatic.php +++ b/Neos.Flow/Classes/Annotations/CompileStatic.php @@ -17,6 +17,7 @@ * @Annotation * @DoctrineAnnotation\Target({"METHOD"}) */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class CompileStatic { } diff --git a/Neos.Flow/Classes/Annotations/Entity.php b/Neos.Flow/Classes/Annotations/Entity.php index 544c1cbd7a..8a8ad24873 100644 --- a/Neos.Flow/Classes/Annotations/Entity.php +++ b/Neos.Flow/Classes/Annotations/Entity.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Marks an object as an entity. * @@ -18,13 +20,15 @@ * with that. * * @Annotation + * @NamedArgumentConstructor * @Target("CLASS") */ +#[\Attribute(\Attribute::TARGET_CLASS)] final class Entity { /** * Name of the repository class to use for managing the entity. - * @var string + * @var string|null */ public $repositoryClass; @@ -33,4 +37,10 @@ final class Entity * @var boolean */ public $readOnly = false; + + public function __construct(?string $repositoryClass = null, bool $readOnly = false) + { + $this->repositoryClass = $repositoryClass; + $this->readOnly = $readOnly; + } } diff --git a/Neos.Flow/Classes/Annotations/FlushesCaches.php b/Neos.Flow/Classes/Annotations/FlushesCaches.php index 63c7275476..b019aec483 100644 --- a/Neos.Flow/Classes/Annotations/FlushesCaches.php +++ b/Neos.Flow/Classes/Annotations/FlushesCaches.php @@ -19,6 +19,7 @@ * @Annotation * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class FlushesCaches { } diff --git a/Neos.Flow/Classes/Annotations/Identity.php b/Neos.Flow/Classes/Annotations/Identity.php index e5b1c4af9d..abef2089bb 100644 --- a/Neos.Flow/Classes/Annotations/Identity.php +++ b/Neos.Flow/Classes/Annotations/Identity.php @@ -23,6 +23,7 @@ * @Annotation * @Target("PROPERTY") */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] final class Identity { } diff --git a/Neos.Flow/Classes/Annotations/IgnoreValidation.php b/Neos.Flow/Classes/Annotations/IgnoreValidation.php index e3174b0677..52d833a040 100644 --- a/Neos.Flow/Classes/Annotations/IgnoreValidation.php +++ b/Neos.Flow/Classes/Annotations/IgnoreValidation.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to ignore validation on a specific method argument or class property. * @@ -18,8 +20,10 @@ * processing, the "evaluate" option can be set to true (while still ignoring any validation error). * * @Annotation + * @NamedArgumentConstructor * @Target({"METHOD", "PROPERTY"}) */ +#[\Attribute(\Attribute::TARGET_METHOD|\Attribute::TARGET_PROPERTY|\Attribute::IS_REPEATABLE)] final class IgnoreValidation { /** @@ -34,18 +38,9 @@ final class IgnoreValidation */ public $evaluate = false; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $argumentName, bool $evaluate = false) { - if (isset($values['value']) || isset($values['argumentName'])) { - $this->argumentName = ltrim(isset($values['argumentName']) ? $values['argumentName'] : $values['value'], '$'); - } - - if (isset($values['evaluate'])) { - $this->evaluate = (boolean)$values['evaluate']; - } + $this->argumentName = ltrim($argumentName, '$'); + $this->evaluate = $evaluate; } } diff --git a/Neos.Flow/Classes/Annotations/Inject.php b/Neos.Flow/Classes/Annotations/Inject.php index d85b4390d6..067524c7cc 100644 --- a/Neos.Flow/Classes/Annotations/Inject.php +++ b/Neos.Flow/Classes/Annotations/Inject.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to enable property injection. * @@ -18,8 +20,10 @@ * to inject a value as specified by the var annotation. * * @Annotation + * @NamedArgumentConstructor * @Target("PROPERTY") */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] final class Inject { /** @@ -35,20 +39,16 @@ final class Inject * This is useful if the object name does not match the class name of the object to be injected: * (at)Inject(name="Some.Package:Some.Virtual.Object") * - * @var string + * @var string|null */ public $name; /** * @param array $values */ - public function __construct(array $values) + public function __construct(?string $name = null, bool $lazy = true) { - if (isset($values['lazy'])) { - $this->lazy = (boolean)$values['lazy']; - } - if (isset($values['name'])) { - $this->name = $values['name']; - } + $this->lazy = $lazy; + $this->name = $name; } } diff --git a/Neos.Flow/Classes/Annotations/InjectConfiguration.php b/Neos.Flow/Classes/Annotations/InjectConfiguration.php index 612326d37a..93cbcbbe04 100644 --- a/Neos.Flow/Classes/Annotations/InjectConfiguration.php +++ b/Neos.Flow/Classes/Annotations/InjectConfiguration.php @@ -12,6 +12,7 @@ */ use Neos\Flow\Configuration\ConfigurationManager; +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * Used to enable property injection for configuration including settings. @@ -20,8 +21,10 @@ * to inject the configured configuration. * * @Annotation + * @NamedArgumentConstructor * @Target("PROPERTY") */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] final class InjectConfiguration { /** @@ -32,7 +35,7 @@ final class InjectConfiguration * * Example: session.name * - * @var string + * @var string|null */ public $path; @@ -44,7 +47,7 @@ final class InjectConfiguration * * Example: Neos.Flow * - * @var string + * @var string|null */ public $package; @@ -55,19 +58,12 @@ final class InjectConfiguration */ public $type = ConfigurationManager::CONFIGURATION_TYPE_SETTINGS; - /** - * @param array $values - */ - public function __construct(array $values) + public function __construct(?string $path = null, ?string $package = null, ?string $type = null) { - if (isset($values['value']) || isset($values['path'])) { - $this->path = isset($values['path']) ? (string)$values['path'] : (string)$values['value']; - } - if (isset($values['package'])) { - $this->package = (string)$values['package']; - } - if (isset($values['type'])) { - $this->type = (string)$values['type']; + $this->path = $path; + $this->package = $package; + if ($type !== null) { + $this->type = $type; } } } diff --git a/Neos.Flow/Classes/Annotations/Internal.php b/Neos.Flow/Classes/Annotations/Internal.php index c994ff03ab..dca5d3cc4a 100644 --- a/Neos.Flow/Classes/Annotations/Internal.php +++ b/Neos.Flow/Classes/Annotations/Internal.php @@ -20,6 +20,7 @@ * @Annotation * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Internal { } diff --git a/Neos.Flow/Classes/Annotations/Introduce.php b/Neos.Flow/Classes/Annotations/Introduce.php index c6aced9e08..2378ffecf0 100644 --- a/Neos.Flow/Classes/Annotations/Introduce.php +++ b/Neos.Flow/Classes/Annotations/Introduce.php @@ -11,50 +11,43 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Introduces the given interface or property into any target class matching * the given pointcut expression. * * @Annotation + * @NamedArgumentConstructor * @Target({"CLASS", "PROPERTY"}) */ +#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::TARGET_PROPERTY|\Attribute::IS_REPEATABLE)] final class Introduce { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $pointcutExpression; /** * The interface name to introduce. - * @var string + * @var string|null */ public $interfaceName; /** * The trait name to introduce * - * @var string + * @var string|null */ public $traitName; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $pointcutExpression, ?string $interfaceName = null, ?string $traitName = null) { - if (!isset($values['value']) && !isset($values['pointcutExpression'])) { - throw new \InvalidArgumentException('An Introduce annotation must specify a pointcut expression.', 1318456624); - } - $this->pointcutExpression = isset($values['pointcutExpression']) ? $values['pointcutExpression'] : $values['value']; - - if (isset($values['interfaceName'])) { - $this->interfaceName = $values['interfaceName']; - } - if (isset($values['traitName'])) { - $this->traitName = $values['traitName']; - } + $this->pointcutExpression = $pointcutExpression; + $this->interfaceName = $interfaceName; + $this->traitName = $traitName; } } diff --git a/Neos.Flow/Classes/Annotations/Lazy.php b/Neos.Flow/Classes/Annotations/Lazy.php index 647f354745..d3a1a41b3d 100644 --- a/Neos.Flow/Classes/Annotations/Lazy.php +++ b/Neos.Flow/Classes/Annotations/Lazy.php @@ -20,6 +20,7 @@ * @Annotation * @Target({"CLASS", "PROPERTY"}) */ +#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::TARGET_PROPERTY)] final class Lazy { } diff --git a/Neos.Flow/Classes/Annotations/MapRequestBody.php b/Neos.Flow/Classes/Annotations/MapRequestBody.php index 4d25e4503a..af57b16828 100644 --- a/Neos.Flow/Classes/Annotations/MapRequestBody.php +++ b/Neos.Flow/Classes/Annotations/MapRequestBody.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to map the request body to a single action argument. * @@ -18,24 +20,21 @@ * map the full body into a single argument without wrapping the request body. * * @Annotation + * @NamedArgumentConstructor * @Target({"METHOD"}) */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class MapRequestBody { /** * Name of the argument to map the request body into. (Can be given as anonymous argument.) * @var string + * @Required */ public $argumentName; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $argumentName) { - if (isset($values['value']) || isset($values['argumentName'])) { - $this->argumentName = ltrim($values['argumentName'] ?? $values['value'], '$'); - } + $this->argumentName = ltrim($argumentName, '$'); } } diff --git a/Neos.Flow/Classes/Annotations/Pointcut.php b/Neos.Flow/Classes/Annotations/Pointcut.php index 5d0c0dc3aa..dfcdeca633 100644 --- a/Neos.Flow/Classes/Annotations/Pointcut.php +++ b/Neos.Flow/Classes/Annotations/Pointcut.php @@ -11,30 +11,28 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Declares a named pointcut. The annotated method does not become an advice * but can be used as a named pointcut instead of the given expression. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Pointcut { /** * The pointcut expression. (Can be given as anonymous argument.) * @var string + * @Required */ public $expression; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(string $expression) { - if (!isset($values['value']) && !isset($values['expression'])) { - throw new \InvalidArgumentException('A Pointcut annotation must specify a pointcut expression.', 1318456604); - } - $this->expression = isset($values['expression']) ? $values['expression'] : $values['value']; + $this->expression = $expression; } } diff --git a/Neos.Flow/Classes/Annotations/Proxy.php b/Neos.Flow/Classes/Annotations/Proxy.php index 6308de927f..e7e709d811 100644 --- a/Neos.Flow/Classes/Annotations/Proxy.php +++ b/Neos.Flow/Classes/Annotations/Proxy.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to disable proxy building for an object. * @@ -18,8 +20,10 @@ * on the object. * * @Annotation + * @NamedArgumentConstructor * @Target("CLASS") */ +#[\Attribute(\Attribute::TARGET_CLASS)] final class Proxy { /** @@ -28,15 +32,8 @@ final class Proxy */ public $enabled = true; - /** - * @param array $values - */ - public function __construct(array $values) + public function __construct(bool $enabled = true) { - if (isset($values['enabled'])) { - $this->enabled = (boolean)$values['enabled']; - } elseif (isset($values['value'])) { - $this->enabled = (boolean)$values['value']; - } + $this->enabled = $enabled; } } diff --git a/Neos.Flow/Classes/Annotations/Scope.php b/Neos.Flow/Classes/Annotations/Scope.php index da2f12d98d..29c0f39a40 100644 --- a/Neos.Flow/Classes/Annotations/Scope.php +++ b/Neos.Flow/Classes/Annotations/Scope.php @@ -11,12 +11,16 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to set the scope of an object. * * @Annotation + * @NamedArgumentConstructor * @Target("CLASS") */ +#[\Attribute(\Attribute::TARGET_CLASS)] final class Scope { /** @@ -25,13 +29,10 @@ final class Scope */ public $value = 'prototype'; - /** - * @param array $values - */ - public function __construct(array $values) + public function __construct(?string $value = null) { - if (isset($values['value'])) { - $this->value = $values['value']; + if ($value !== null) { + $this->value = $value; } } } diff --git a/Neos.Flow/Classes/Annotations/Session.php b/Neos.Flow/Classes/Annotations/Session.php index 128f69b7f7..8cd8c47203 100644 --- a/Neos.Flow/Classes/Annotations/Session.php +++ b/Neos.Flow/Classes/Annotations/Session.php @@ -11,13 +11,17 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Used to control the behavior of session handling when the annotated * method is called. * * @Annotation + * @NamedArgumentConstructor * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Session { /** @@ -25,4 +29,9 @@ final class Session * @var boolean */ public $autoStart = false; + + public function __construct(bool $autoStart = false) + { + $this->autoStart = $autoStart; + } } diff --git a/Neos.Flow/Classes/Annotations/Signal.php b/Neos.Flow/Classes/Annotations/Signal.php index edd6b43ce4..92c8b097c1 100644 --- a/Neos.Flow/Classes/Annotations/Signal.php +++ b/Neos.Flow/Classes/Annotations/Signal.php @@ -21,6 +21,7 @@ * @Annotation * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class Signal { } diff --git a/Neos.Flow/Classes/Annotations/SkipCsrfProtection.php b/Neos.Flow/Classes/Annotations/SkipCsrfProtection.php index 4aab98a988..e8034a701f 100644 --- a/Neos.Flow/Classes/Annotations/SkipCsrfProtection.php +++ b/Neos.Flow/Classes/Annotations/SkipCsrfProtection.php @@ -22,6 +22,7 @@ * @Annotation * @Target("METHOD") */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class SkipCsrfProtection { } diff --git a/Neos.Flow/Classes/Annotations/Transient.php b/Neos.Flow/Classes/Annotations/Transient.php index 1ffdbbe196..0a897d24f9 100644 --- a/Neos.Flow/Classes/Annotations/Transient.php +++ b/Neos.Flow/Classes/Annotations/Transient.php @@ -21,6 +21,7 @@ * @Annotation * @Target("PROPERTY") */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] final class Transient { } diff --git a/Neos.Flow/Classes/Annotations/Validate.php b/Neos.Flow/Classes/Annotations/Validate.php index c10c72b9bb..9f85537924 100644 --- a/Neos.Flow/Classes/Annotations/Validate.php +++ b/Neos.Flow/Classes/Annotations/Validate.php @@ -11,17 +11,21 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Controls how a property or method argument will be validated by Flow. * * @Annotation + * @NamedArgumentConstructor * @Target({"METHOD", "PROPERTY"}) */ +#[\Attribute(\Attribute::TARGET_METHOD|\Attribute::TARGET_PROPERTY|\Attribute::IS_REPEATABLE)] final class Validate { /** * The validator type, either a FQCN or a Flow validator class name. - * @var string + * @var string|null */ public $type; @@ -43,27 +47,18 @@ final class Validate */ public $validationGroups = ['Default']; - /** - * @param array $values - * @throws \InvalidArgumentException - */ - public function __construct(array $values) + public function __construct(?string $argumentName = null, string $type = null, array $options = [], ?array $validationGroups = null) { - if (!isset($values['type'])) { - throw new \InvalidArgumentException('Validate annotations must be given a validator type.', 1318494791); - } - $this->type = $values['type']; + $this->type = $type; - if (isset($values['options']) && is_array($values['options'])) { - $this->options = $values['options']; - } + $this->options = $options; - if (isset($values['value']) || isset($values['argumentName'])) { - $this->argumentName = ltrim(isset($values['argumentName']) ? $values['argumentName'] : $values['value'], '$'); + if ($argumentName !== null) { + $this->argumentName = ltrim($argumentName, '$'); } - if (isset($values['validationGroups']) && is_array($values['validationGroups'])) { - $this->validationGroups = $values['validationGroups']; + if ($validationGroups !== null) { + $this->validationGroups = $validationGroups; } } } diff --git a/Neos.Flow/Classes/Annotations/ValidationGroups.php b/Neos.Flow/Classes/Annotations/ValidationGroups.php index 7a1ebccacd..fcac75c8b6 100644 --- a/Neos.Flow/Classes/Annotations/ValidationGroups.php +++ b/Neos.Flow/Classes/Annotations/ValidationGroups.php @@ -12,11 +12,14 @@ */ use Doctrine\Common\Annotations\Annotation as DoctrineAnnotation; +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation + * @NamedArgumentConstructor * @DoctrineAnnotation\Target({"METHOD"}) */ +#[\Attribute(\Attribute::TARGET_METHOD)] final class ValidationGroups { /** @@ -25,15 +28,8 @@ final class ValidationGroups */ public $validationGroups = ['Default', 'Controller']; - /** - * @param array $values - */ - public function __construct(array $values) + public function __construct(array $validationGroups) { - if (isset($values['validationGroups']) && is_array($values['validationGroups'])) { - $this->validationGroups = $values['validationGroups']; - } elseif (isset($values['value']) && is_array($values['value'])) { - $this->validationGroups = $values['value']; - } + $this->validationGroups = $validationGroups; } } diff --git a/Neos.Flow/Classes/Annotations/ValueObject.php b/Neos.Flow/Classes/Annotations/ValueObject.php index 2357027e70..5061fc5060 100644 --- a/Neos.Flow/Classes/Annotations/ValueObject.php +++ b/Neos.Flow/Classes/Annotations/ValueObject.php @@ -11,6 +11,8 @@ * source code. */ +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * Marks the annotate class as a value object. * @@ -20,8 +22,10 @@ * of the value object. * * @Annotation + * @NamedArgumentConstructor * @Target("CLASS") */ +#[\Attribute(\Attribute::TARGET_CLASS)] final class ValueObject { /** @@ -29,4 +33,9 @@ final class ValueObject * @var boolean */ public $embedded = true; + + public function __construct(bool $embedded = true) + { + $this->embedded = $embedded; + } } diff --git a/Neos.Flow/Classes/ObjectManagement/Proxy/Compiler.php b/Neos.Flow/Classes/ObjectManagement/Proxy/Compiler.php index 0a23ef525c..932580715b 100644 --- a/Neos.Flow/Classes/ObjectManagement/Proxy/Compiler.php +++ b/Neos.Flow/Classes/ObjectManagement/Proxy/Compiler.php @@ -285,6 +285,29 @@ protected function stripOpeningPhpTag($classCode) } + /** + * Render the source (string) form of a PHP Attribute. + * @param \ReflectionAttribute $attribute + * @return string + */ + public static function renderAttribute($attribute): string + { + $attributeAsString = '\\' . $attribute->getName(); + if (count($attribute->getArguments()) > 0) { + $argumentsAsString = []; + foreach ($attribute->getArguments() as $argumentName => $argumentValue) { + $renderedArgumentValue = var_export($argumentValue, true); + if (is_numeric($argumentName)) { + $argumentsAsString[] = $renderedArgumentValue; + } else { + $argumentsAsString[] = "$argumentName: $renderedArgumentValue"; + } + } + $attributeAsString .= '(' . implode(', ', $argumentsAsString) . ')'; + } + return "#[$attributeAsString]"; + } + /** * Render the source (string) form of an Annotation instance. * diff --git a/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyClass.php b/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyClass.php index a0c50d3218..ea26c89de0 100644 --- a/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyClass.php +++ b/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyClass.php @@ -162,6 +162,7 @@ public function addConstant($name, $valueCode) */ public function addProperty($name, $initialValueCode, $visibility = 'private', $docComment = '') { + // TODO: Add support for PHP attributes? $this->properties[$name] = [ 'initialValueCode' => $initialValueCode, 'visibility' => $visibility, @@ -259,6 +260,12 @@ protected function buildClassDocumentation() $classDocumentation .= " * @codeCoverageIgnore\n"; $classDocumentation .= " */\n"; + if (PHP_MAJOR_VERSION >= 8) { + foreach ($classReflection->getAttributes() as $attribute) { + $classDocumentation .= Compiler::renderAttribute($attribute) . "\n"; + } + } + return $classDocumentation; } diff --git a/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyMethod.php b/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyMethod.php index 5ec925c187..74fb6c4800 100644 --- a/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyMethod.php +++ b/Neos.Flow/Classes/ObjectManagement/Proxy/ProxyMethod.php @@ -12,6 +12,7 @@ */ use Neos\Flow\Annotations as Flow; +use Neos\Flow\Reflection\MethodReflection; use Neos\Flow\Reflection\ReflectionService; /** @@ -238,9 +239,17 @@ protected function buildMethodDocumentation($className, $methodName) foreach ($methodAnnotations as $annotation) { $methodDocumentation .= ' * ' . Compiler::renderAnnotation($annotation) . "\n"; } - } + $methodDocumentation .= " */\n"; - $methodDocumentation .= " */\n"; + if (PHP_MAJOR_VERSION >= 8) { + $method = new MethodReflection($className, $methodName); + foreach ($method->getAttributes() as $attribute) { + $methodDocumentation .= ' ' . Compiler::renderAttribute($attribute) . "\n"; + } + } + } else { + $methodDocumentation .= " */\n"; + } return $methodDocumentation; } diff --git a/Neos.Flow/Classes/Reflection/ReflectionService.php b/Neos.Flow/Classes/Reflection/ReflectionService.php index 66d539d753..ec16d7dd80 100644 --- a/Neos.Flow/Classes/Reflection/ReflectionService.php +++ b/Neos.Flow/Classes/Reflection/ReflectionService.php @@ -226,6 +226,13 @@ class ReflectionService */ protected $initialized = false; + /** + * A runtime cache for reflected method annotations to speed up repeating checks. + * + * @var array + */ + protected $methodAnnotationsRuntimeCache = []; + /** * Sets the status cache * @@ -789,14 +796,25 @@ public function isMethodAnnotatedWith($className, $methodName, $annotationClassN */ public function getMethodAnnotations($className, $methodName, $annotationClassName = null) { - if (!$this->initialized) { - $this->initialize(); - } $className = $this->cleanClassName($className); $annotationClassName = $annotationClassName === null ? null : $this->cleanClassName($annotationClassName); + $methodAnnotations = $this->methodAnnotationsRuntimeCache[$className][$methodName] ?? null; $annotations = []; - $methodAnnotations = $this->annotationReader->getMethodAnnotations(new MethodReflection($className, $methodName)); + if ($methodAnnotations === null) { + if (!$this->initialized) { + $this->initialize(); + } + + $method = new MethodReflection($className, $methodName); + $methodAnnotations = $this->annotationReader->getMethodAnnotations($method); + if (PHP_MAJOR_VERSION >= 8) { + foreach ($method->getAttributes() as $attribute) { + $methodAnnotations[] = $attribute->newInstance(); + } + } + $this->methodAnnotationsRuntimeCache[$className][$methodName] = $methodAnnotations; + } if ($annotationClassName === null) { return $methodAnnotations; } @@ -1251,6 +1269,13 @@ protected function reflectClass($className) $this->annotatedClasses[$annotationClassName][$className] = true; $this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS][] = $annotation; } + if (PHP_MAJOR_VERSION >= 8) { + foreach ($class->getAttributes() as $attribute) { + $annotationClassName = $attribute->getName(); + $this->annotatedClasses[$annotationClassName][$className] = true; + $this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS][] = $attribute->newInstance(); + } + } /** @var $property PropertyReflection */ foreach ($class->getProperties() as $property) { @@ -1292,6 +1317,11 @@ public function reflectClassProperty($className, PropertyReflection $property) foreach ($this->annotationReader->getPropertyAnnotations($property, $propertyName) as $annotation) { $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][get_class($annotation)][] = $annotation; } + if (PHP_MAJOR_VERSION >= 8) { + foreach ($property->getAttributes() as $attribute) { + $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][$attribute->getName()][] = $attribute->newInstance(); + } + } return $visibility; } diff --git a/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/ClassWithPhpAttributes.php b/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/ClassWithPhpAttributes.php new file mode 100644 index 0000000000..b046377fc3 --- /dev/null +++ b/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/ClassWithPhpAttributes.php @@ -0,0 +1,24 @@ + 'bar'])] +class ClassWithPhpAttributes +{ +} diff --git a/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/SampleAttribute.php b/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/SampleAttribute.php new file mode 100644 index 0000000000..e826410549 --- /dev/null +++ b/Neos.Flow/Tests/Functional/ObjectManagement/Fixtures/SampleAttribute.php @@ -0,0 +1,16 @@ +class = $class; + $this->options = $options; + $this->argWithDefault = $argWithDefault; + } +} diff --git a/Neos.Flow/Tests/Functional/ObjectManagement/ProxyCompilerTest.php b/Neos.Flow/Tests/Functional/ObjectManagement/ProxyCompilerTest.php index 196d441c35..8a08571ece 100644 --- a/Neos.Flow/Tests/Functional/ObjectManagement/ProxyCompilerTest.php +++ b/Neos.Flow/Tests/Functional/ObjectManagement/ProxyCompilerTest.php @@ -129,4 +129,19 @@ public function proxiedFinalClassesAreStillFinal() $reflectionClass = new ClassReflection(Fixtures\FinalClassWithDependencies::class); self::assertTrue($reflectionClass->isFinal()); } + + /** + * @test + */ + public function attributesArePreserved() + { + if (PHP_MAJOR_VERSION < 8) { + $this->markTestSkipped('Only for PHP 8 with Attributes'); + } + $reflectionClass = new ClassReflection(Fixtures\ClassWithPhpAttributes::class); + $attributes = $reflectionClass->getAttributes(); + self::assertCount(2, $attributes); + self::assertEquals(Fixtures\SampleAttribute::class, $attributes[0]->getName()); + self::assertEquals(Fixtures\ClassWithPhpAttributes::class, $attributes[0]->getArguments()[0]); + } } diff --git a/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/CompilerTest.php b/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/CompilerTest.php index e5c518ac9d..7de4a02770 100644 --- a/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/CompilerTest.php +++ b/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/CompilerTest.php @@ -45,11 +45,11 @@ public function annotationsAndStrings() $sessionWithAutoStart->autoStart = true; return [ [ - new Signal([]), + new Signal(), '@\Neos\Flow\Annotations\Signal' ], [ - new Scope(['value' => 'singleton']), + new Scope('singleton'), '@\Neos\Flow\Annotations\Scope("singleton")' ], [ @@ -69,27 +69,27 @@ public function annotationsAndStrings() '@\Neos\Flow\Annotations\Session' ], [ - new Validate(['value' => 'foo1', 'type' => 'bar1']), + new Validate('foo1', 'bar1'), '@\Neos\Flow\Annotations\Validate(type="bar1", argumentName="foo1")' ], [ - new Validate(['type' => 'bar1', 'options' => ['minimum' => 2]]), + new Validate(null, 'bar1', ['minimum' => 2]), '@\Neos\Flow\Annotations\Validate(type="bar1", options={ "minimum"=2 })' ], [ - new Validate(['type' => 'bar1', 'options' => ['foo' => ['bar' => 'baz']]]), + new Validate(null, 'bar1', ['foo' => ['bar' => 'baz']]), '@\Neos\Flow\Annotations\Validate(type="bar1", options={ "foo"={ "bar"="baz" } })' ], [ - new Validate(['type' => 'bar1', 'options' => ['foo' => 'hubbabubba', 'bar' => true]]), + new Validate(null, 'bar1', ['foo' => 'hubbabubba', 'bar' => true]), '@\Neos\Flow\Annotations\Validate(type="bar1", options={ "foo"="hubbabubba", "bar"=true })' ], [ - new Validate(['type' => 'bar1', 'options' => [new Inject([])]]), + new Validate(null, 'bar1', [new Inject()]), '@\Neos\Flow\Annotations\Validate(type="bar1", options={ @\Neos\Flow\Annotations\Inject })' ], [ - new Validate(['type' => 'bar1', 'options' => [new Validate(['type' => 'bar1', 'options' => ['foo' => 'hubbabubba']])]]), + new Validate(null, 'bar1', [new Validate(null, 'bar1', ['foo' => 'hubbabubba'])]), '@\Neos\Flow\Annotations\Validate(type="bar1", options={ @\Neos\Flow\Annotations\Validate(type="bar1", options={ "foo"="hubbabubba" }) })' ], ]; diff --git a/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/ProxyMethodTest.php b/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/ProxyMethodTest.php index 525d66137b..3e52de0ee3 100644 --- a/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/ProxyMethodTest.php +++ b/Neos.Flow/Tests/Unit/ObjectManagement/Proxy/ProxyMethodTest.php @@ -22,15 +22,15 @@ class ProxyMethodTest extends \Neos\Flow\Tests\UnitTestCase */ public function buildMethodDocumentationAddsAllExpectedAnnotations() { - $validateFoo1 = new Flow\Validate(['value' => 'foo1', 'type' => 'bar1']); - $validateFoo2 = new Flow\Validate(['value' => 'foo2', 'type' => 'bar2']); + $validateFoo1 = new Flow\Validate('foo1', 'bar1'); + $validateFoo2 = new Flow\Validate('foo2', 'bar2'); $mockReflectionService = $this->getMockBuilder(ReflectionService::class)->disableOriginalConstructor()->getMock(); $mockReflectionService->expects(self::any())->method('hasMethod')->will(self::returnValue(true)); - $mockReflectionService->expects(self::any())->method('getMethodTagsValues')->with('My\Class\Name', 'myMethod')->will(self::returnValue([ + $mockReflectionService->expects(self::any())->method('getMethodTagsValues')->with('My\ClassName', 'myMethod')->will(self::returnValue([ 'param' => ['string $name'] ])); - $mockReflectionService->expects(self::any())->method('getMethodAnnotations')->with('My\Class\Name', 'myMethod')->will(self::returnValue([ + $mockReflectionService->expects(self::any())->method('getMethodAnnotations')->with('My\ClassName', 'myMethod')->will(self::returnValue([ $validateFoo1, $validateFoo2, new Flow\SkipCsrfProtection([]) @@ -38,7 +38,8 @@ public function buildMethodDocumentationAddsAllExpectedAnnotations() $mockProxyMethod = $this->getAccessibleMock(ProxyMethod::class, ['dummy'], [], '', false); $mockProxyMethod->injectReflectionService($mockReflectionService); - $methodDocumentation = $mockProxyMethod->_call('buildMethodDocumentation', 'My\Class\Name', 'myMethod'); + eval('namespace My; class ClassName { public function myMethod() {} }'); + $methodDocumentation = $mockProxyMethod->_call('buildMethodDocumentation', 'My\ClassName', 'myMethod'); $expected = ' /**' . chr(10) . diff --git a/Neos.Flow/Tests/Unit/Validation/ValidatorResolverTest.php b/Neos.Flow/Tests/Unit/Validation/ValidatorResolverTest.php index 27824ff95a..7fa5ea28d9 100644 --- a/Neos.Flow/Tests/Unit/Validation/ValidatorResolverTest.php +++ b/Neos.Flow/Tests/Unit/Validation/ValidatorResolverTest.php @@ -276,19 +276,19 @@ public function buildMethodArgumentsValidatorConjunctionsBuildsAConjunctionFromV ]; $validateAnnotations = [ - new Annotations\Validate([ - 'type' => 'Foo', - 'options' => ['bar' => 'baz'], - 'argumentName' => '$arg1' - ]), - new Annotations\Validate([ - 'type' => 'Bar', - 'argumentName' => '$arg1' - ]), - new Annotations\Validate([ - 'type' => 'TYPO3\TestPackage\Quux', - 'argumentName' => '$arg2' - ]), + new Annotations\Validate( + '$arg1', + 'Foo', + ['bar' => 'baz'] + ), + new Annotations\Validate( + '$arg1', + 'Bar' + ), + new Annotations\Validate( + '$arg2', + 'Neos\TestPackage\Quux' + ), ]; $mockReflectionService = $this->getMockBuilder(ReflectionService::class)->disableOriginalConstructor()->getMock(); @@ -315,7 +315,7 @@ public function buildMethodArgumentsValidatorConjunctionsBuildsAConjunctionFromV ['array'], ['Foo', ['bar' => 'baz']], ['Bar'], - ['TYPO3\TestPackage\Quux'] + ['Neos\TestPackage\Quux'] ) ->willReturnOnConsecutiveCalls( $conjunction1, @@ -324,7 +324,7 @@ public function buildMethodArgumentsValidatorConjunctionsBuildsAConjunctionFromV $mockArrayValidator, $mockFooValidator, $mockBarValidator, - $mockQuuxValidator, + $mockQuuxValidator ); $validatorResolver->_set('reflectionService', $mockReflectionService); @@ -375,10 +375,10 @@ public function buildMethodArgumentsValidatorConjunctionsThrowsExceptionIfValida ] ]; $validateAnnotations = [ - new Annotations\Validate([ - 'type' => 'Neos\TestPackage\Quux', - 'argumentName' => '$arg2' - ]), + new Annotations\Validate( + '$arg2', + 'Neos\TestPackage\Quux' + ), ]; $mockReflectionService = $this->getMockBuilder(ReflectionService::class)->disableOriginalConstructor()->getMock(); @@ -584,21 +584,25 @@ public function buildBaseValidatorConjunctionAddsValidatorsDefinedByAnnotationsI ]; $validateAnnotations = [ 'foo' => [ - new Annotations\Validate([ - 'type' => 'Foo', - 'options' => ['bar' => 'baz'], - ]), - new Annotations\Validate([ - 'type' => 'Bar', - ]), - new Annotations\Validate([ - 'type' => 'Baz', - ]), + new Annotations\Validate( + null, + 'Foo', + ['bar' => 'baz'] + ), + new Annotations\Validate( + null, + 'Bar' + ), + new Annotations\Validate( + null, + 'Baz' + ), ], 'bar' => [ - new Annotations\Validate([ - 'type' => 'Neos\TestPackage\Quux', - ]), + new Annotations\Validate( + null, + 'Neos\TestPackage\Quux' + ), ], ]; @@ -621,11 +625,11 @@ public function buildBaseValidatorConjunctionAddsValidatorsDefinedByAnnotationsI ->withConsecutive( [get_class($mockObject), 'foo', Annotations\Validate::class], [get_class($mockObject), 'bar', Annotations\Validate::class], - [get_class($mockObject), 'baz', Annotations\Validate::class], + [get_class($mockObject), 'baz', Annotations\Validate::class] )->willReturnOnConsecutiveCalls( $validateAnnotations['foo'], $validateAnnotations['bar'], - [], + [] ); $mockObjectManager = $this->createMock(ObjectManagerInterface::class); $mockObjectManager->method('get')->with(ReflectionService::class)->willReturn($mockReflectionService);