Skip to content

Commit

Permalink
AttributeFilterChain: added support for constructor arguments in defa…
Browse files Browse the repository at this point in the history
…ult filters
  • Loading branch information
JoshyPHP committed Mar 23, 2024
1 parent 5a93731 commit a3aa589
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 43 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@
"test": "phpunit --exclude-group ''"
},
"extra": {
"version": "2.16.1-dev"
"version": "2.17.0-dev"
}
}
9 changes: 5 additions & 4 deletions docs/testdox.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,14 @@ Attribute Collection (s9e\TextFormatter\Tests\Configurator\Collections\Attribute
[x] Throws an exception when accessing an Attribute that does not exist

Attribute Filter Chain (s9e\TextFormatter\Tests\Configurator\Collections\AttributeFilterChain)
[x] append() throws an InvalidArgumentException on invalid callbacks
[x] prepend() throws an InvalidArgumentException on invalid callbacks
[x] append() throws a RuntimeException on invalid callbacks
[x] prepend() throws a RuntimeException on invalid callbacks
[x] append() throws an InvalidArgumentException on uncallable callbacks
[x] prepend() throws an InvalidArgumentException on uncallable callbacks
[x] PHP string callbacks are normalized to an instance of AttributeFilter
[x] PHP array callbacks are normalized to an instance of AttributeFilter
[x] Default filters such as "#int" are normalized to an instance of the corresponding AttributeFilter
[x] Default filters accept positional constructor arguments
[x] Instances of AttributeFilter are added as-is
[x] Automatically parses callback parameters

Expand Down Expand Up @@ -458,8 +459,8 @@ Tag Collection (s9e\TextFormatter\Tests\Configurator\Collections\TagCollection)
[x] Throws an exception when accessing a Tag that does not exist

Tag Filter Chain (s9e\TextFormatter\Tests\Configurator\Collections\TagFilterChain)
[x] append() throws an InvalidArgumentException on invalid callbacks
[x] prepend() throws an InvalidArgumentException on invalid callbacks
[x] append() throws a RuntimeException on invalid callbacks
[x] prepend() throws a RuntimeException on invalid callbacks
[x] append() throws an InvalidArgumentException on uncallable callbacks
[x] prepend() throws an InvalidArgumentException on uncallable callbacks
[x] PHP string callbacks are normalized to an instance of TagFilter
Expand Down
27 changes: 9 additions & 18 deletions src/Configurator/Collections/AttributeFilterChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,20 @@
*/
namespace s9e\TextFormatter\Configurator\Collections;

use Override;
use s9e\TextFormatter\Configurator\Items\Filter;

class AttributeFilterChain extends FilterChain
{
/**
* {@inheritdoc}
*/
public function getFilterClassName()
#[Override]
protected function getDefaultFilter(string $filterName, array $constructorArgs = []): Filter
{
return 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter';
return AttributeFilterCollection::getDefaultFilter($filterName, $constructorArgs);
}

/**
* Normalize a value into an AttributeFilter instance
*
* @param mixed $value Either a valid callback or an instance of AttributeFilter
* @return \s9e\TextFormatter\Configurator\Items\AttributeFilter Normalized filter
*/
public function normalizeValue($value)
#[Override]
public function getFilterClassName()
{
if (is_string($value) && preg_match('(^#\\w+$)', $value))
{
$value = AttributeFilterCollection::getDefaultFilter(substr($value, 1));
}

return parent::normalizeValue($value);
return 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter';
}
}
66 changes: 58 additions & 8 deletions src/Configurator/Collections/FilterChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace s9e\TextFormatter\Configurator\Collections;

use InvalidArgumentException;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\FilterHelper;
use s9e\TextFormatter\Configurator\Items\Filter;
use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
Expand Down Expand Up @@ -51,23 +52,33 @@ public function containsCallback(callable $callback)
*/
public function normalizeValue($value)
{
if (is_string($value) && strpos($value, '(') !== false)
{
return $this->createFilter($value);
}

$className = $this->getFilterClassName();
if ($value instanceof $className)
{
return $value;
}

if (!is_callable($value))
if (is_callable($value))
{
throw new InvalidArgumentException('Filter ' . var_export($value, true) . ' is neither callable nor an instance of ' . $className);
return new $className($value);
}

return new $className($value);
if (is_string($value))
{
return $this->createFilter($value);
}

throw new InvalidArgumentException('Filter ' . var_export($value, true) . ' is neither callable nor an instance of ' . $className);
}

protected function createDefaultFilter(string $filterString)
{
$config = FilterHelper::parse($filterString);

$constructorArgs = $this->getConstructorArgsFromFilterParams($config['params'] ?? []);
$filterName = substr($config['filter'], 1);

return $this->getDefaultFilter($filterName, $constructorArgs);
}

/**
Expand All @@ -78,6 +89,11 @@ public function normalizeValue($value)
*/
protected function createFilter($filterString)
{
if ($filterString[0] === '#')
{
return $this->createDefaultFilter($filterString);
}

$config = FilterHelper::parse($filterString);
$filter = $this->normalizeValue($config['filter']);
if (isset($config['params']))
Expand All @@ -92,4 +108,38 @@ protected function createFilter($filterString)

return $filter;
}

protected function getConstructorArgsFromFilterParams(array $params): array
{
$constructorArgs = [];
foreach ($params as $i => $param)
{
[$type, $value] = $param;
if ($type !== 'Value')
{
throw new RuntimeException('Cannot use default filter with named parameters');
}

if (isset($param[2]))
{
// Named argument
$constructorArgs[$param[2]] = $value;
}
else
{
// Positional argument
$constructorArgs[] = $value;
}
}

return $constructorArgs;
}

/**
* Get an instance of a default filter
*/
protected function getDefaultFilter(string $filterName, array $constructorArgs = []): Filter
{
throw new RuntimeException(get_class($this) . ' has no default filters');
}
}
34 changes: 28 additions & 6 deletions tests/Configurator/Collections/AttributeFilterChainTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ private function privateMethod() {}
public function doNothing() {}

/**
* @testdox append() throws an InvalidArgumentException on invalid callbacks
* @testdox append() throws a RuntimeException on invalid callbacks
*/
public function testAppendInvalidCallback()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage("Filter '*invalid*' is neither callable nor an instance of s9e\\TextFormatter\\Configurator\\Items\\AttributeFilter");
$this->expectException('RuntimeException');
$this->expectExceptionMessage("Cannot parse '*invalid*'");

$filterChain = new AttributeFilterChain;
$filterChain->append('*invalid*');
}

/**
* @testdox prepend() throws an InvalidArgumentException on invalid callbacks
* @testdox prepend() throws a RuntimeException on invalid callbacks
*/
public function testPrependInvalidCallback()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage("Filter '*invalid*' is neither callable nor an instance of s9e\\TextFormatter\\Configurator\\Items\\AttributeFilter");
$this->expectException('RuntimeException');
$this->expectExceptionMessage("Cannot parse '*invalid*'");

$filterChain = new AttributeFilterChain;
$filterChain->prepend('*invalid*');
Expand Down Expand Up @@ -103,6 +103,28 @@ public function testDefaultFilter()
);
}

/**
* @testdox Default filters accept positional constructor arguments
*/
public function testDefaultFilterPositionalConstructorArguments()
{
$filterChain = new AttributeFilterChain;
$filter = $filterChain->append('#range(1, 12)');

$this->assertEquals(['min' => 1, 'max' => 12], $filter->getVars());
}

// /**
// * @testdox Default filters accept named constructor arguments
// */
// public function testDefaultFilterNamedConstructorArguments()
// {
// $filterChain = new AttributeFilterChain;
// $filter = $filterChain->append('#range(max: 12, min: 3)');
//
// $this->assertEquals(['min' => 3, 'max' => 12], $filter->getVars());
// }

/**
* @testdox Instances of AttributeFilter are added as-is
*/
Expand Down
12 changes: 6 additions & 6 deletions tests/Configurator/Collections/TagFilterChainTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ private function privateMethod() {}
public function doNothing() {}

/**
* @testdox append() throws an InvalidArgumentException on invalid callbacks
* @testdox append() throws a RuntimeException on invalid callbacks
*/
public function testAppendInvalidCallback()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage("Filter '*invalid*' is neither callable nor an instance of s9e\\TextFormatter\\Configurator\\Items\\TagFilter");
$this->expectException('RuntimeException');
$this->expectExceptionMessage("Cannot parse '*invalid*'");

$filterChain = new TagFilterChain;
$filterChain->append('*invalid*');
}

/**
* @testdox prepend() throws an InvalidArgumentException on invalid callbacks
* @testdox prepend() throws a RuntimeException on invalid callbacks
*/
public function testPrependInvalidCallback()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage("Filter '*invalid*' is neither callable nor an instance of s9e\\TextFormatter\\Configurator\\Items\\TagFilter");
$this->expectException('RuntimeException');
$this->expectExceptionMessage("Cannot parse '*invalid*'");

$filterChain = new TagFilterChain;
$filterChain->prepend('*invalid*');
Expand Down

0 comments on commit a3aa589

Please sign in to comment.