LastDragon-ru / lara-asp

Awesome Set of Packages for Laravel
MIT License
11 stars 1 forks source link

Run filters programmaticaly #40

Closed webard closed 2 years ago

webard commented 2 years ago

In version before 1.0.0 I had trait:

<?php

declare(strict_types=1);

namespace Core\Abilities;

use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Collection;
use LastDragon_ru\LaraASP\GraphQL\SearchBy\SearchBuilder;

trait Filterable
{
    private Container $operatorsContainer;

    /**
     * @param EloquentBuilder<Model>|QueryBuilder $query
     * @param array<mixed> $filters
     * @return EloquentBuilder<Model>|QueryBuilder
     */
    public function scopeFilter(EloquentBuilder|QueryBuilder $query, array $filters): EloquentBuilder|QueryBuilder
    {
        $this->operatorsContainer = app(Container::class);
        $operators = (new Collection([
            0 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\AllOf::class,
            1 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\AnyOf::class,
            2 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Logical\Not::class,
            3 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Complex\Relation::class,
            4 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Equal::class,
            5 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\NotEqual::class,
            6 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\In::class,
            7 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\NotIn::class,
            8 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\IsNull::class,
            9 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\IsNotNull::class,
            10 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Like::class,
            11 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\NotLike::class,
            12 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Contains::class,
            13 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\StartsWith::class,
            14 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\EndsWith::class,
            15 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\LessThan::class,
            16 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\LessThanOrEqual::class,
            17 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\GreaterThan::class,
            18 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\GreaterThanOrEqual::class,
            19 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\Between::class,
            20 => \LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators\Comparison\NotBetween::class,
        ]))
            ->map(
                function (
                    string $operator
                ): \LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComparisonOperator|\LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\ComplexOperator|\LastDragon_ru\LaraASP\GraphQL\SearchBy\Contracts\LogicalOperator {
                    return $this->operatorsContainer->make($operator);
                }
            )
            ->all();

        return (new SearchBuilder($operators))->build($query, $filters);
    }
}

which allows me to run filters on models like this:

User::filter([
    'anyOf' => [
        [
            'allOf' => [
                [
                    'anyOf' => [
                        [
                            'email' => [
                                'like' => "%gmail%"
                            ]
                        ],
                        [
                            'email' => [
                                'like' => "%live%"
                            ]
                        ]
                    ]
                ],
            ]
        ]
    ]
]);

On version 1.0.0, filter mechanism was rewritten, and code stops work. Is there a possibility to run filters programmaticaly? I know it is "side functionality", but it is very helpful to store filters in database as JSON.

LastDragon-ru commented 2 years ago

In v1 operators converted into directives (#18) and use Argument instead of array. Technically, you can try apply filters something like:

https://github.com/LastDragon-ru/lara-asp/blob/32c5f9f6de44957b89508b130d827cc56e7af0d5/packages/graphql/src/SearchBy/Directives/DirectiveTest.php#L226-L230

but, if I remember correctly, it will not throw an error if SearchByConditionTest is not a valid type name that makes writing code harder.

webard commented 2 years ago

Your way, there is an Exception:

Conditions for `LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive` are invalid.

My code:

<?php

declare(strict_types=1);

namespace Core\Abilities;

use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Eloquent\{Builder as EloquentBuilder, Model};
use Illuminate\Database\Query\Builder as QueryBuilder;
use GraphQL\Language\Parser;
use LastDragon_ru\LaraASP\GraphQL\SearchBy\Directives\Directive;

trait Filterable
{
    private Container $operatorsContainer;

    /**
     * @param EloquentBuilder<Model>|QueryBuilder $query
     * @param array<mixed> $filters
     *
     * @return EloquentBuilder<Model>|QueryBuilder
     */
    public function scopeFilter(EloquentBuilder|QueryBuilder $builderFactory, array $filters): EloquentBuilder|QueryBuilder
    {
        $definitionNode = Parser::inputValueDefinition('input: SearchByConditionTest');
        $directiveNode  = Parser::directive('@test');
        $directive      = app()->make(Directive::class)->hydrate($directiveNode, $definitionNode);
        $builder        = $builderFactory;
        $actual         = $directive->handleBuilder($builder, $filters);

        return $actual;
    }
}

and usage:

$data = User::filter(
        [

            'anyOf' => [
                [
                    'email' => [
                        'equal' => "%gmail%"
                    ]
                ],
                [
                    'email' => [
                        'equal' => "%live%"
                    ]
                ]
            ]
        ],

    )->first();

Looks like $filters must be ArgumentSet, but is array.

LastDragon-ru commented 2 years ago

Instead of SearchByConditionTest you should use the actual type same as in users query. If type invalid (or $filters array has a wrong structure) generated Argument will have invalid structure => query will fail.

You can try to play/debug the following code to check that type name/$filter valid (directive doing the same):

$definitionNode = Parser::inputValueDefinition('input: SearchByConditionTest');
$argument       = $this->getFactory()->getArgument($definitionNode, $filter);
webard commented 2 years ago

Ok, seems to work, but now it is necessary to have filter input for every Model.

Thank you for help.

LastDragon-ru commented 2 years ago

You are welcome)