Skip to content

Collection function syntax #768

@stepapo

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions