Skip to content

Collection function syntax #768

Description

@stepapo

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;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Enhancement.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions