Collection function syntax
It would be nice to call Collection Functions directly in property string if applicable.
Instead of doing:
$authors->findBy([CountAggregateFunction::class, 'books->id', 2]),
I would like to do:
$authors->findBy(['books->id:count' => 2]).
Or even instead of:
$authors->findBy([CompareGreaterThanFunction::class, [CountAggregateFunction::class, 'books->id'], 2]),
just call:
$authors->findBy(['books->id:count>' => 2]).
For filtering, this is actually easily achievable outside of library with custom ConditionParser:
class CustomConditionParser extends ConditionParser
{
public function parsePropertyOperator(string $condition): array
{
// language=PhpRegExp
$regexp = '#^(?P<path>' . self::PATH_REGEXP . ')(:(?P<function>\w+))?(?P<operator>!=|<=|>=|=|>|<|~)?$#';
if (preg_match($regexp, $condition, $matches) !== 1) {
return [CompareEqualsFunction::class, $condition];
}
$operator = $matches['operator'] ?? '=';
$function = $matches['function'] ?? null;
$condition = $this->parsePropertyFunction($condition);
# TODO detect if custom function expects operator for comparison
return match ($operator) {
'=' => [CompareEqualsFunction::class, $condition],
'!=' => [CompareNotEqualsFunction::class, $condition],
'>=' => [CompareGreaterThanEqualsFunction::class, $condition],
'>' => [CompareGreaterThanFunction::class, $condition],
'<=' => [CompareSmallerThanEqualsFunction::class, $condition],
'<' => [CompareSmallerThanFunction::class, $condition],
'~' => [CompareLikeFunction::class, $condition],
default => throw new InvalidStateException,
};
}
public function parsePropertyFunction(string $propertyPath): array|string
{
// language=PhpRegExp
$regexp = '#^(?P<path>' . self::PATH_REGEXP . ')(:(?P<function>\w+))?(?P<operator>!=|<=|>=|=|>|<|~)?$#';
if (preg_match($regexp, $propertyPath, $matches) !== 1) {
throw new InvalidArgumentException('Unsupported condition format.');
}
$path = $matches['path'];
$function = $matches['function'] ?? null;
return $function ? match ($function) {
'avg' => [AvgAggregateFunction::class, $path],
'count' => [CountAggregateFunction::class, $path],
'max' => [MaxAggregateFunction::class, $path],
'min' => [MinAggregateFunction::class, $path],
'sum' => [SumAggregateFunction::class, $path],
default => [$function, $path],
} : $path;
}
}
For sorting, I did not find a way to allow this without altering library:
$authors->orderBy('books->id:count').
If this is not planned to be part of the library, I would at least like sorting in Collection to make use of ConditionParser, somehow like this:
public function orderBy($expression, string $direction = ICollection::ASC): ICollection
{
$expression = $this->normalizeExpression($expression);
...
}
private function normalizeExpression(array|string $expression): array|string
{
$parser = $this->mapper->getRepository()->getConditionParser();
if (is_string($expression)) {
return $parser->parsePropertyFunction($expression);
# TODO support operator in sorting
}
if (isset($expression[0])) {
return $expression;
}
$normalizedExpression = [];
# TODO parse array expression
return $normalizedExpression;
}
If this is not planned to be part of the library,
Actually, this is the first proposal to make it "incorporated" this way. Definitely interesting.
Though at first sight, it goes a bit against the idea that the expression is similar to actual PHP (-> instead of .).
I would at least like sorting in Collection to make use of ConditionParser
That seems like a reasonable request, even though the order expression is not a "condition". As for the implementation, no idea if this is the way; I'll have to dig into it a bit more. Starting a PR would be a good option, I think.
it goes a bit against the idea that the expression is similar to actual PHP (
->instead of.).
Perhaps books->id::count or count(books->id) would be better. Although the latter suggests support for functions accepting multiple properties/other arguments, which seems harder to achieve.
order expression is not a "condition".
In the end, even sorting already uses ConditionParser::parsePropertyExpr, but that does not seem like the right method to use for this.
Starting a PR would be a good option, I think.
I can look into it some more as well.