Issues (32)

src/Builder/WhereTaxonomyClause.php (4 issues)

1
<?php
2
3
namespace Sanderdekroon\Parlant\Builder;
4
5
use Closure;
6
use InvalidArgumentException;
7
8
class WhereTaxonomyClause
9
{
10
11
    protected $grammar;
12
    protected $relation;
13
14
15 4
    public function __construct($grammar)
16
    {
17 4
        $this->grammar = $grammar;
18 4
    }
19
20
21 4
    public function build($taxonomy, $field = null, $operator = null, $value = null, $includeChildren = true, $relation = null, $level = 1)
22
    {
23
        /** @todo Rewrite */
24 4
        $this->relation = [$level => empty($relation) ? 'AND' : $relation];
25
26
        // If the taxonomy is an array, we will assume it is an array of key-value pairs
27
        // and can add them each as a where clause. We will maintain the boolean we
28
        // received when the method was called and pass it into the nested where.
29 4
        if (is_array($taxonomy)) {
30
            return $this->addArrayOfWhereTaxonomies($taxonomy);
31
        }
32
33
        // If the taxonomy parameter is a close we'll start a nested meta query.
34 4
        if ($taxonomy instanceof Closure) {
35 1
            $nestedTaxonomy = $this->extractNestedTaxonomyClosures($taxonomy);
36
            
37 1
            return [$this->whereNestedTaxonomy($nestedTaxonomy, $relation)];
0 ignored issues
show
The call to Sanderdekroon\Parlant\Bu...::whereNestedTaxonomy() has too many arguments starting with $relation. ( Ignorable by Annotation )

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

37
            return [$this->/** @scrutinizer ignore-call */ whereNestedTaxonomy($nestedTaxonomy, $relation)];

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
38
        }
39
40
        // If the field variable is a closure, we'll start a nested taxonomy query
41
        // and use the supplied taxonomy as default taxonomy in the query.
42 4
        if ($field instanceof Closure) {
43
            $nestedTaxonomy = $this->extractNestedTaxonomyClosures($field, $taxonomy);
44
            
45
            return [$this->whereNestedTaxonomy($nestedTaxonomy, $relation)];
46
        }
47
48
        // Here we will make some assumptions about the operator. If only 3 values are
49
        // passed to the method, we will assume that the operator is an equals sign
50
        // and keep going. Otherwise, we'll require the operator to be passed in.
51 4
        list($value, $operator) = $this->prepareValueAndOperator(
52 4
            $value,
53 4
            $operator,
54 4
            (func_num_args() == 3 || is_null($value)),
55 4
            true // This is needed since tax_query has different default operator.
56
        );
57
58
        // If the given operator is not found in the list of valid operators we will
59
        // assume that the developer is just short-cutting the '=' operators and
60
        // we will set the operators to '=' and set the values appropriately.
61 4
        if ($this->invalidOperator($operator)) {
62
            list($value, $operator) = [$operator, 'IN'];
63
        }
64
65
        // Validate the supplied field against the known fields of an taxonomy
66
        // query within WP_Query. If no valid field is found, we'll default
67
        // back to the WordPress default which is term_id.
68 4
        $field = $this->getValidTermField($field);
69
70 4
        return [compact(
71 4
            'taxonomy',             // Taxonomy name
72 4
            'field',                // Taxonomy term field
73 4
            'operator',             // The term operator
74 4
            'value',                // The value of the term
75 4
            'includeChildren',      // Include/exclude children
76 4
            'level'                 // Unimplemented, undocumented.
77
        )];
78
79
        return $this;
0 ignored issues
show
return $this is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
80
    }
81
82
83 4
    public function getRelation()
84
    {
85 4
        return $this->relation;
86
    }
87
88
    /**
89
     * Resolve the closures and replace them with NestedTaxonomy classes.
90
     * @param  Closure $closure
91
     * @return NestedTaxonomy
92
     */
93 1
    protected function extractNestedTaxonomyClosures($closure, $taxonomyName = null)
94
    {
95 1
        $nestedTaxonomy = call_user_func($closure->bindTo(new NestedTaxonomy($taxonomyName)));
96
97 1
        $query = $nestedTaxonomy->getQuery();
98 1
        foreach ($query as $key => $taxonomy) {
99 1
            if ($taxonomy['taxonomy'] instanceof Closure) {
100 1
                $query[$key] = $this->extractNestedTaxonomyClosures($taxonomy['taxonomy']);
101
            }
102
        }
103
104 1
        $nestedTaxonomy->replaceQuery($query);
105 1
        return $nestedTaxonomy;
106
    }
107
108
    /**
109
     * Nest multiple taxonomy queries by supplying a query. If the closure contains
110
     * another closure, it is resolved recursivly.
111
     * @param  Closure $closure
112
     * @return NestedTaxonomy          Returns a NestedTaxonomy instance which is further processed by the compiler.
113
     */
114 1
    protected function whereNestedTaxonomy($nestedTaxonomy)
115
    {
116 1
        if (!$nestedTaxonomy instanceof NestedTaxonomy) {
117
            throw new InvalidArgumentException('Invalid class supplied for nested taxonomy query');
118
        }
119
120 1
        $query = $nestedTaxonomy->getQuery();
121
122 1
        foreach ($query as $key => $taxonomy) {
123 1
            if ($taxonomy instanceof NestedTaxonomy) {
124
                $query[$key] = $this->whereNestedTaxonomy($taxonomy);
125
                continue;
126
            }
127
128 1
            $query[$key] = $this->parseNestedTaxonomy($taxonomy);
129
        }
130
131 1
        $nestedTaxonomy->replaceQuery($query);
132
133 1
        return $nestedTaxonomy;
134
    }
135
136
    /**
137
     * Parse the nested taxonomy fields by validating them. If the taxonomy fields is
138
     * an instance of NestedTaxonomy, we'll resolve that recursively.
139
     * @param  array|NestedTaxonomy $taxonomy
140
     * @return array
141
     */
142 1
    protected function parseNestedTaxonomy($taxonomy)
143
    {
144 1
        if ($taxonomy instanceof NestedTaxonomy) {
145
            $taxonomy = $this->whereNestedTaxonomy($taxonomy);
146
        }
147
148 1
        return $this->validateTaxonomyFields($taxonomy);
0 ignored issues
show
It seems like $taxonomy can also be of type Sanderdekroon\Parlant\Builder\NestedTaxonomy; however, parameter $fields of Sanderdekroon\Parlant\Bu...alidateTaxonomyFields() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

148
        return $this->validateTaxonomyFields(/** @scrutinizer ignore-type */ $taxonomy);
Loading history...
149
    }
150
151
    /**
152
     * Do some basic validating of the meta fields. Checks if the operator is valid and
153
     * if the meta type (if supplied any) is valid with the current grammar.
154
     * @param  array $fields
155
     * @return array
156
     */
157 1
    protected function validateTaxonomyFields($fields)
158
    {
159 1
        extract($fields);
160
161
        // If the given operator is not found in the list of valid operators we will
162
        // assume that the developer is just short-cutting the '=' operators and
163
        // we will set the operators to '=' and set the values appropriately.
164 1
        if ($this->invalidOperator($operator)) {
165
            list($value, $operator) = [$operator, '='];
166
        }
167
168
        // If no field is given or if it's an invalid one, we'll default back
169
        // to the term_id field. The $field is checked against the
170
        // values in the supplied posttype grammar.
171 1
        $field = $this->getValidTermField($field);
172
173 1
        return compact('taxonomy', 'field', 'operator', 'value', 'includeChildren', 'level');
174
    }
175
176
    /**
177
     * Adds arrays of where metas to the query.
178
     * @param array $taxonomyArray
179
     */
180
    protected function addArrayOfWhereTaxonomies($taxonomyArray)
181
    {
182
        $build = [];
183
        foreach ($taxonomyArray as $array) {
184
            $build[] = $this->build(...$array);
0 ignored issues
show
$array is expanded, but the parameter $taxonomy of Sanderdekroon\Parlant\Bu...TaxonomyClause::build() does not expect variable arguments. ( Ignorable by Annotation )

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

184
            $build[] = $this->build(/** @scrutinizer ignore-type */ ...$array);
Loading history...
185
        }
186
187
        return $build;
188
    }
189
190
    /**
191
     * Return a valid taxonomy term field, like term_id or term_slug.
192
     * @param  string $type
193
     * @return string       Returns 'term_id' if none is supplied or if it's invalid
194
     */
195 4
    protected function getValidTermField($field = null)
196
    {
197 4
        if (is_null($field) || !in_array($field, $this->grammar->getTaxonomyFields())) {
198 1
            return 'term_id';
199
        }
200
201 3
        return strtolower($field);
202
    }
203
204
    // protected abstract function setBinding($key, $data);
205
    
206
    // protected abstract function getBinding($key);
207
208
    // protected abstract function getGrammar();
209
    
210
    // protected abstract function appendBinding($key, $data);
211
    
212 4
    protected function invalidOperator($operator)
213
    {
214 4
        return !in_array($operator, $this->grammar->getTaxonomyOperators());
215
    }
216
    
217 4
    protected function prepareValueAndOperator($value, $operator, $useDefault = false, $termDefault = false)
218
    {
219 4
        if ($useDefault) {
220 3
            return [$operator, $termDefault ? 'IN' : '='];
221
        }
222
223 1
        if ($this->invalidOperator($operator) && !is_null($value)) {
224
            throw new InvalidArgumentException('Illegal operator and value combination.');
225
        }
226
227 1
        return [$value, $operator];
228
    }
229
}
230