Passed
Push — master ( bb0e2b...eaa23b )
by Stephen
01:06 queued 10s
created

HasFilters::isFilterableAttribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
51
            // Check every parameter to see if there's a corresponding Filter class
52
            foreach ($filters ?? $this->filters as $filterName => $value) {
53
54
                // Apply Filter class if it exists and is a filterable attribute
55
                $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

55
                /** @scrutinizer ignore-call */ 
56
                $query = self::applyFilter($query, $filterName, $value);
Loading history...
56
            }
57
        });
58
59
        return $builder;
60
    }
61
62
    /**
63
     * Apply a filter to a Query if the Filter class is valid.
64
     *
65
     * @param Builder $query
66
     * @param string $filterName
67
     * @param mixed $filterValue
68
     * @param Filter $decorator
69
     * @return Builder
70
     */
71
    private function applyFilter(Builder $query, string $filterName, $filterValue = null, $decorator = null)
72
    {
73
        // Get the Filter class if none is provided
74
        $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

74
        $decorator = $decorator ?? self::/** @scrutinizer ignore-call */ getFilterClass($filterName);
Loading history...
75
76
        // Apply Filter class if it exists and is a filterable attribute
77
        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

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