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