jeudi 7 juillet 2022

Make modification to method argument before the method gets called

What would be the best way to perform some modification on a method argument before the method is actually executed?

I have tried achieving this with a combination of attributes and use of the decorator/proxy pattern:

#[\Attribute]
class Query
{
}

class Foo
{
   #[Query]
   public function request(array $query = [])
   {
   }

   public function foo(array $query = [])
   {
   }

   #[Query]
   public function bar(string $name, int $age, array $query = [])
   {
   }
}

class FooDecorator
{
   private Foo $target;

   public function __construct(Foo $target)
   {
      $this->target = $target;
   }

   public function __call(string $methodName, array $args)
   {
      $class = get_class($this->target);
      try {
        $reflection = new \ReflectionClass($class);
        $methods = $reflection->getMethods();
        $attributeName = __NAMESPACE__ . '\Query';
        foreach ($methods as $method) {
            if ($method->getName() !== $methodName) {
                continue;
            }
            $attributes = $method->getAttributes();
            foreach ($attributes as $attribute) {
                if ($attribute->getName() === $attributeName) {
                    $parameters = $method->getParameters();
                    foreach ($parameters as $key => $param) {
                        if ($param->getName() === 'query') {
                            // Here we filter the parameter named 'query'
                            $args[$key] = $this->validateQueryParameter($args[$key]);
                            break;
                        }
                    }
                }
            }
        }
      } catch (\Exception $e) {
      }
      if (method_exists($this->target, $methodName)) {
          return call_user_func_array([$this->target, $methodName], $args);
      }
   }

   private function validateQueryParameter(array $query): array
   {
      $allowed = [
        'foo',
        'bar',
      ];
      $query = array_filter($query = array_change_key_case($query), function ($key) use ($allowed) {
        // Filter out any non-allowed keys
        return in_array($key, $allowed);
      }, ARRAY_FILTER_USE_KEY);
      return $query;
   }
}

$foo = new FooDecorator(new Foo());
// Should remove faz & baz, but keep foo in the query
$foo->query(['faz' => 1, 'baz' => 2, 'foo' => 3]);
// Should not perform anything on the query parameter
$foo->foo(['baz' => 1]);
// Should remove faz & baz, but keep foo in the query
$foo->bar('foo', 100, ['faz' => 1, 'baz' => 2, 'foo' => 3]);

This works as expected, but since I am using a decorator here I am now missing the hints for each method included in the Foo class.

I know that I could use an interface and declare all methods included in `Foo´, but then I would need to implement every method (the real class contains many many methods) in the decorator, which seems like overkill.





Aucun commentaire:

Enregistrer un commentaire