skip to Main Content

I have a project that use attributes :

    #[OperationQueryParam('id', IntegerOperation::class, requirements: new Numeric(min: 1, max: 2_147_483_647))]

Everything works fine but there are multiple attribute that have the same requirement argument new Numeric(min: 1, max: 2_147_483_647) so i wanted to create a static function in my numeric class :

    final public static function createPostiveInt(): self {
       return new self(min:1, max:2_147_483_647);
    }

then call the static function instead of the direct instanciation of the Numeric object to avoid repeating the constructor parameters.

    #[OperationQueryParam('id', IntegerOperation::class, requirements: Numeric::createPostiveInt())]

but i have this error : Compile Error: Constant expression contains invalid operations

Am i missing something ? Why does a direct instanciation works but not the static function ?

EDIT :

using define(POSITIVE_INT,Numeric::createPostiveInt());
works but it seems dirty.

2

Answers


  1. Depending on how the project uses reflection to get attribute values, it might be possible for you to subclass OperationQueryParam and include your constant values via parent::__construct(). To do this, the reflecting code must not check for simple equality on class name, but check for it being an instance of it. There’s a couple of ways to do this, but one option is to use the second
    parameter to getAttributes() which is ReflectionAttribute::IS_INSTANCEOF

    Below is a quick PoC. We have a base attribute which requires a $name parameter, and then our custom attribute which doesn’t, and instead includes that in the parent constructor.

    #[Attribute]
    class BaseAttribute
    {
        public function __construct(public readonly string $name)
        {
        }
    }
    
    #[Attribute]
    class ConstantlyNameAttribute extends BaseAttribute
    {
        public function __construct()
        {
            parent::__construct('Chris');
        }
    }
    
    #[ConstantlyNameAttribute]
    class Thing{}
    
    $reflectionClass = new ReflectionClass(Thing::class);
    
    // Doesn't work
    var_dump(getName($reflectionClass->getAttributes(BaseAttribute::class)));
    
    // Works
    var_dump(getName($reflectionClass->getAttributes(BaseAttribute::class, ReflectionAttribute::IS_INSTANCEOF)));
    
    // Helper function
    function getName(array $reflectionAttributes) : ?string
    {
        if(!count($reflectionAttributes)){
            return null;
        }
        
        $attribute = array_pop($reflectionAttributes);
        return $attribute->newInstance()->name;
    }
    

    Demo: https://3v4l.org/SWTa6#v8.3.8

    If the project is a third-party and you can’t modify things, it might be worth it kicking this request upstream to see if they are willing to modify it.

    Login or Signup to reply.
  2. From https://www.php.net/manual/en/functions.arguments.php:

    Default parameter values may be scalar values, arrays, the special
    type null, and as of PHP 8.1.0, objects using the new ClassName()
    syntax.

    In other words, you can’t use a run-time construct like a function call as a default value for a parameter. You could extend the Numeric class here, to build-in your common defaults:

    class MyNumeric extends Numeric
    {
        public function __construct(int $min = 1, int $max = 2_147_483_647) {
            parent::__construct(min: $min, max: $max);
        }
    }
    

    Or even just remove the args completely:

    class MyNumeric extends Numeric
    {
        public function __construct() {
            parent::__construct(min: 1, max: 2_147_483_647);
        }
    }
    

    And then your definition becomes:

    #[OperationQueryParam(
        'id',
        IntegerOperation::class,
        requirements: new MyNumeric()
    )]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search