Skip to content

Commit

Permalink
FilterExecutor: refactoring, aware-filters can be dynamic
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 2, 2023
1 parent 6086b97 commit 46ae380
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 51 deletions.
94 changes: 43 additions & 51 deletions src/Latte/Runtime/FilterExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,10 @@ public function getAll(): array
*/
public function __get(string $name): callable
{
if (isset($this->_static[$name])) {
[$callback, $aware] = $this->prepareFilter($name);
if ($aware) { // FilterInfo aware filter
return $this->$name = function (...$args) use ($callback) {
array_unshift($args, $info = new FilterInfo);
if ($args[1] instanceof HtmlStringable) {
$args[1] = $args[1]->__toString();
$info->contentType = ContentType::Html;
}

$res = $callback(...$args);
return $info->contentType === ContentType::Html
? new Html($res)
: $res;
};
} else { // classic filter
return $this->$name = $callback;
}
}

// dynamic filter
foreach ($this->_dynamic as $loader) {
$callback = $loader($name);
if ($callback !== null) {
$this->_static[$name] = [$callback, null];
return $this->__get($name);
}
}

$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
? ", did you mean '$t'?"
: '.';
throw new \LogicException("Filter '$name' is not defined$hint");
[$callback, $infoAware] = $this->prepareFilter($name);
return $this->$name = $infoAware
? fn(...$args) => $this->callInfoAwareAsClassic($callback, ...$args)
: $callback;
}


Expand All @@ -100,31 +71,23 @@ public function __get(string $name): callable
*/
public function filterContent(string $name, FilterInfo $info, mixed ...$args): mixed
{
if (!isset($this->_static[$name])) {
$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
? ", did you mean '$t'?"
: '.';
throw new \LogicException("Filter |$name is not defined$hint");
}

[$callback, $aware] = $this->prepareFilter($name);

if ($info->contentType === ContentType::Html && $args[0] instanceof HtmlStringable) {
$args[0] = $args[0]->__toString();
}

if ($aware) { // FilterInfo aware filter
[$callback, $infoAware] = $this->prepareFilter($name);
if ($infoAware) {
array_unshift($args, $info);
return $callback(...$args);
}

// classic filter
if ($info->contentType !== ContentType::Text) {
throw new Latte\RuntimeException("Filter |$name is called with incompatible content type " . strtoupper($info->contentType)
throw new Latte\RuntimeException("Filter |$name is called with incompatible content type " . strtoupper($info->contentType ?? 'NULL')
. ($info->contentType === ContentType::Html ? ', try to prepend |stripHtml.' : '.'));
}

$res = ($this->$name)(...$args);
$res = $callback(...$args);
if ($res instanceof HtmlStringable) {
trigger_error("Filter |$name should be changed to content-aware filter.");
$info->contentType = ContentType::Html;
Expand All @@ -140,13 +103,42 @@ public function filterContent(string $name, FilterInfo $info, mixed ...$args): m
*/
private function prepareFilter(string $name): array
{
if (!isset($this->_static[$name][1])) {
$params = Helpers::toReflection($this->_static[$name][0])->getParameters();
$this->_static[$name][1] = $params
&& $params[0]->getType() instanceof \ReflectionNamedType
&& $params[0]->getType()->getName() === FilterInfo::class;
if (isset($this->_static[$name])) {
$this->_static[$name][1] ??= $this->isInfoAware($this->_static[$name][0]);
return $this->_static[$name];
}

foreach ($this->_dynamic as $loader) {
if ($callback = $loader($name)) {
return $this->_static[$name] = [$callback, $this->isInfoAware($callback)];
}
}

$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
? ", did you mean '$t'?"
: '.';
throw new \LogicException("Filter '$name' is not defined$hint");
}


private function isInfoAware(callable $filter): bool
{
$params = Helpers::toReflection($filter)->getParameters();
return $params && (string) $params[0]->getType() === FilterInfo::class;
}


private function callInfoAwareAsClassic(callable $filter, mixed ...$args): mixed
{
array_unshift($args, $info = new FilterInfo);
if ($args[1] instanceof HtmlStringable) {
$args[1] = $args[1]->__toString();
$info->contentType = ContentType::Html;
}

return $this->_static[$name];
$res = $filter(...$args);
return $info->contentType === ContentType::Html
? new Html($res)
: $res;
}
}
17 changes: 17 additions & 0 deletions tests/common/FilterExecutor.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ test('', function () {
});


test('', function () {
$filters = new FilterExecutor;
$filters->add(null, function ($name) use ($filters) {
if ($name === 'dynamic') {
return fn(FilterInfo $info, ...$vals) => $name . ',' . implode(',', $vals);
}
});
Assert::same('dynamic,x,y', $filters->filterContent('dynamic', new FilterInfo, 'x', 'y'));

Assert::exception(
fn() => $filters->filterContent('unknown', new FilterInfo, ''),
LogicException::class,
"Filter 'unknown' is not defined.",
);
});


test('', function () {
$filters = new FilterExecutor;

Expand Down

0 comments on commit 46ae380

Please sign in to comment.