HasFilters   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 138
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 19
eloc 22
c 4
b 1
f 0
dl 0
loc 138
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A filterQuery() 0 10 3
A isFilterableAttribute() 0 6 2
A isValidFiltersArray() 0 3 2
A applyFilter() 0 11 3
A isValidFilterClass() 0 3 3
A getFilterClass() 0 5 3
A getAttributeFilter() 0 3 1
A applyFilters() 0 12 2
1
<?php
2
3
namespace Sfneal\Queries\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Sfneal\Filters\Filter;
7
8
trait HasFilters
9
{
10
    // todo: improve return type hinting
11
12
    /**
13
     * Retrieve an array of model attribute keys & corresponding Filter class values.
14
     *
15
     * @return array
16
     */
17
    abstract protected function queryFilters(): array;
18
19
    /**
20
     * Apply pre-defined Filters to a Query.
21
     *
22
     * @param  Builder  $builder
23
     * @param  null  $filters
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $filters is correct as it would always require null to be passed?
Loading history...
24
     * @return Builder
25
     */
26
    protected function filterQuery(Builder $builder, $filters = null)
27
    {
28
        // Check if a single filter was passed
29
        if (! is_null($filters) && is_string($filters)) {
0 ignored issues
show
introduced by
The condition is_null($filters) is always true.
Loading history...
30
            return self::applyFilter($builder, $filters);
31
        }
32
33
        // Working with an array of filters
34
        else {
35
            return self::applyFilters($builder, $filters);
0 ignored issues
show
Bug Best Practice introduced by
The method Sfneal\Queries\Traits\HasFilters::applyFilters() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

35
            return self::/** @scrutinizer ignore-call */ applyFilters($builder, $filters);
Loading history...
36
        }
37
    }
38
39
    /**
40
     * Apply Filter decorators to the query if both the parameter is given and the Filter class exists.
41
     *
42
     * @param  Builder  $builder
43
     * @param  array|null  $filters
44
     * @return Builder
45
     */
46
    private function applyFilters(Builder $builder, array $filters = null)
47
    {
48
        // Wrap scopes
49
        $builder->where(function (Builder $query) use ($filters) {
50
            // Check every parameter to see if there's a corresponding Filter class
51
            foreach ($filters ?? $this->filters as $filterName => $value) {
52
                // Apply Filter class if it exists and is a filterable attribute
53
                $query = self::applyFilter($query, $filterName, $value);
0 ignored issues
show
Bug Best Practice introduced by
The method Sfneal\Queries\Traits\HasFilters::applyFilter() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
                /** @scrutinizer ignore-call */ 
54
                $query = self::applyFilter($query, $filterName, $value);
Loading history...
54
            }
55
        });
56
57
        return $builder;
58
    }
59
60
    /**
61
     * Apply a filter to a Query if the Filter class is valid.
62
     *
63
     * @param  Builder  $query
64
     * @param  string  $filterName
65
     * @param  mixed  $filterValue
66
     * @param  Filter  $decorator
67
     * @return Builder
68
     */
69
    private function applyFilter(Builder $query, string $filterName, $filterValue = null, $decorator = null)
70
    {
71
        // Get the Filter class if none is provided
72
        $decorator = $decorator ?? self::getFilterClass($filterName);
0 ignored issues
show
Bug Best Practice introduced by
The method Sfneal\Queries\Traits\HasFilters::getFilterClass() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
        $decorator = $decorator ?? self::/** @scrutinizer ignore-call */ getFilterClass($filterName);
Loading history...
73
74
        // Apply Filter class if it exists and is a filterable attribute
75
        if (! is_null($decorator) && self::isValidFilterClass($decorator, $filterName)) {
0 ignored issues
show
Bug Best Practice introduced by
The method Sfneal\Queries\Traits\Ha...s::isValidFilterClass() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

75
        if (! is_null($decorator) && self::/** @scrutinizer ignore-call */ isValidFilterClass($decorator, $filterName)) {
Loading history...
76
            $query = (new $decorator)->apply($query, $filterValue);
77
        }
78
79
        return $query;
80
    }
81
82
    /**
83
     * Determine if the Filter decorator class exists and is valid.
84
     *
85
     * Check that the Filter class exists or the $decorator is an object.
86
     * Then check that the attribute is declared as filterable.
87
     *
88
     * @param  string|mixed  $decorator
89
     * @param  string  $attribute
90
     * @return bool
91
     */
92
    private function isValidFilterClass($decorator, string $attribute): bool
93
    {
94
        return (class_exists($decorator) || is_object($decorator)) && $this->isFilterableAttribute($attribute);
95
    }
96
97
    /**
98
     * Create a filter decorator by manipulating filter name to find the corresponding filter class.
99
     *
100
     * @param  $name
101
     * @return null|string|Filter
102
     */
103
    private function getFilterClass($name)
104
    {
105
        // Check if an array of attribute keys and Filter class values is defined
106
        if (self::isValidFiltersArray($this->queryFilters()) && $this->isFilterableAttribute($name)) {
107
            return $this->getAttributeFilter($name);
108
        }
109
    }
110
111
    /**
112
     * Check if the filters array is valid for querying.
113
     *
114
     * @param  $filters
115
     * @return bool
116
     */
117
    private static function isValidFiltersArray($filters)
118
    {
119
        return ! empty($filters) && is_array($filters);
120
    }
121
122
    /**
123
     * Determine if a particular filter is in the array of filterable attributes.
124
     *
125
     * @param  string  $name
126
     * @return bool
127
     */
128
    private function isFilterableAttribute(string $name)
129
    {
130
        if (method_exists($this, 'queryFilters')) {
131
            return in_array($name, array_keys($this->queryFilters()));
132
        } else {
133
            return true;
134
        }
135
    }
136
137
    /**
138
     * Retrieve a Filter that corresponds to an attribute.
139
     *
140
     * @param  string  $name
141
     * @return string
142
     */
143
    private function getAttributeFilter(string $name): string
144
    {
145
        return $this->queryFilters()[$name];
146
    }
147
}
148