Issues (32)

src/Builder/WhereMetaClause.php (7 issues)

1
<?php
2
3
namespace Sanderdekroon\Parlant\Builder;
4
5
use Closure;
6
use InvalidArgumentException;
7
8
class WhereMetaClause
9
{
10
    protected $grammar;
11
12
    protected $relation;
13
14 8
    public function __construct($grammar)
15
    {
16 8
        $this->grammar = $grammar;
17 8
    }
18
19
    /**
20
     * Query the meta values (custom post fields) of posts.
21
     * @param  string|array|Closure $column         The field name, an array of where clauses or an Closure detailing a nested where clause.
22
     * @param  string       $operator
23
     * @param  mixed        $value
24
     * @param  string       $type           The type comparison, for example NUMERIC or CHAR
25
     * @param  string       $relation       AND/OR, currently unimplemented
26
     * @param  integer      $level          The query level, currently unimplemented
27
     * @return $this
28
     */
29 8
    public function build($column, $operator = null, $value = null, $type = null, $relation = null, $level = 1)
30
    {
31
        /** @todo Rewrite */
32 8
        $this->relation = [$level => empty($relation) ? 'AND' : $relation];
33
34
        // If the column is an array, we will assume it is an array of key-value pairs
35
        // and can add them each as a where clause. We will maintain the boolean we
36
        // received when the method was called and pass it into the nested where.
37 8
        if (is_array($column)) {
38
            return $this->addArrayOfWhereMetas($column);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->addArrayOfWhereMetas($column) returns the type array which is incompatible with the documented return type Sanderdekroon\Parlant\Builder\WhereMetaClause.
Loading history...
39
        }
40
41
        // If the column parameter is a closure we'll start a nested meta query.
42 8
        if ($column instanceof Closure) {
43 1
            $nestedMetas = $this->extractNestedMetaClosures($column);
44 1
            return $this->whereNestedMeta($nestedMetas);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->whereNestedMeta($nestedMetas) returns the type Sanderdekroon\Parlant\Builder\NestedMeta which is incompatible with the documented return type Sanderdekroon\Parlant\Builder\WhereMetaClause.
Loading history...
45
        }
46
47
        // Here we will make some assumptions about the operator. If only 2 values are
48
        // passed to the method, we will assume that the operator is an equals sign
49
        // and keep going. Otherwise, we'll require the operator to be passed in.
50 8
        list($value, $operator) = $this->prepareValueAndOperator(
51 8
            $value,
52 8
            $operator,
53 8
            (func_num_args() == 2 || is_null($value))
54
        );
55
56
        // If the given operator is not found in the list of valid operators we will
57
        // assume that the developer is just short-cutting the '=' operators and
58
        // we will set the operators to '=' and set the values appropriately.
59 7
        if ($this->invalidOperator($operator)) {
60
            list($value, $operator) = [$operator, '='];
61
        }
62
63
        // If no type is given or if it's an invalid one, we'll default back
64
        // to the CHAR comparing type. The $type is checked against the
65
        // values in the supplied posttype grammar.
66 7
        $type = $this->getValidMetaType($type);
67
68 7
        return [compact(
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(compact('co...tor', 'type', 'level')) returns the type array<integer,array> which is incompatible with the documented return type Sanderdekroon\Parlant\Builder\WhereMetaClause.
Loading history...
69 7
            'column',   // Metakey name
70 7
            'value',    // The actual value
71 7
            'operator', // =, <, >, etc.
72 7
            'type',     // CHAR, BINARY, etc
73 7
            'level'     // Unimplemented, undocumented.
74
        )];
75
    }
76
77 7
    public function getRelation()
78
    {
79 7
        return $this->relation;
80
    }
81
82
    /**
83
     * Resolve the closures and replace them with NestedMeta classes.
84
     * @param  Closure $closure
85
     * @return NestedMeta
86
     */
87 1
    protected function extractNestedMetaClosures($closure)
88
    {
89 1
        $nestedMeta = call_user_func($closure->bindTo(new NestedMeta));
90
91 1
        $query = $nestedMeta->getQuery();
92 1
        foreach ($query as $key => $meta) {
93 1
            if ($meta['column'] instanceof Closure) {
94 1
                $query[$key] = $this->extractNestedMetaClosures($meta['column']);
95
            }
96
        }
97
98 1
        $nestedMeta->replaceQuery($query);
99 1
        return $nestedMeta;
100
    }
101
102
    /**
103
     * Nest multiple meta queries by supplying a query. If the closure contains
104
     * another closure, it is resolved recursivly.
105
     * @param  Closure $closure
106
     * @return NestedMeta          Returns a NestedMeta instance which is further processed by the compiler.
107
     */
108 1
    protected function whereNestedMeta($nestedMeta)
109
    {
110 1
        if (!$nestedMeta instanceof NestedMeta) {
111
            throw new InvalidArgumentException('Invalid class supplied for nested meta query');
112
        }
113
114 1
        $query = $nestedMeta->getQuery();
115
116 1
        foreach ($query as $key => $meta) {
117 1
            if ($meta instanceof NestedMeta) {
118
                $query[$key] = $this->whereNestedMeta($meta);
119
                continue;
120
            }
121
122 1
            $query[$key] = $this->parseNestedMeta($meta);
123
        }
124
125 1
        $nestedMeta->replaceQuery($query);
126 1
        return [$nestedMeta];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($nestedMeta) returns the type array<integer,Sanderdekr...ant\Builder\NestedMeta> which is incompatible with the documented return type Sanderdekroon\Parlant\Builder\NestedMeta.
Loading history...
127
    }
128
129
    /**
130
     * Parse the nested meta fields by validating them. If the meta fields is
131
     * an instance of NestedMeta, we'll resolve that recursively.
132
     * @param  array|NestedMeta $meta
133
     * @return array
134
     */
135 1
    protected function parseNestedMeta($meta)
136
    {
137 1
        if ($meta instanceof NestedMeta) {
138
            $meta = $this->whereNestedMeta($meta);
139
        }
140
141 1
        return $this->validateMetaFields($meta);
0 ignored issues
show
It seems like $meta can also be of type Sanderdekroon\Parlant\Builder\NestedMeta; however, parameter $fields of Sanderdekroon\Parlant\Bu...e::validateMetaFields() 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

141
        return $this->validateMetaFields(/** @scrutinizer ignore-type */ $meta);
Loading history...
142
    }
143
144
    /**
145
     * Do some basic validating of the meta fields. Checks if the operator is valid and
146
     * if the meta type (if supplied any) is valid with the current grammar.
147
     * @param  array $fields
148
     * @return array
149
     */
150 1
    protected function validateMetaFields($fields)
151
    {
152 1
        extract($fields);
153
154
        // If the given operator is not found in the list of valid operators we will
155
        // assume that the developer is just short-cutting the '=' operators and
156
        // we will set the operators to '=' and set the values appropriately.
157 1
        if ($this->invalidOperator($operator)) {
158
            list($value, $operator) = [$operator, '='];
159
        }
160
161
        // If no type is given or if it's an invalid one, we'll default back
162
        // to the CHAR comparing type. The $type is checked against the
163
        // values in the supplied posttype grammar.
164 1
        $type = $this->getValidMetaType($type);
165
166 1
        return compact('column', 'value', 'operator', 'type', 'level');
167
    }
168
169
    /**
170
     * Adds arrays of where metas to the query.
171
     * @param array $metaArray
172
     */
173
    protected function addArrayOfWhereMetas($metaArray)
174
    {
175
        $build = [];
176
        foreach ($metaArray as $array) {
177
            $build = array_merge($build, $this->build(...$array));
0 ignored issues
show
It seems like $this->build($array) can also be of type Sanderdekroon\Parlant\Builder\NestedMeta; however, parameter $array2 of array_merge() does only seem to accept array|null, 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

177
            $build = array_merge($build, /** @scrutinizer ignore-type */ $this->build(...$array));
Loading history...
$array is expanded, but the parameter $column of Sanderdekroon\Parlant\Bu...hereMetaClause::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

177
            $build = array_merge($build, $this->build(/** @scrutinizer ignore-type */ ...$array));
Loading history...
178
        }
179
180
        return $build;
181
    }
182
183
    /**
184
     * Return a valid comparator type, like CHAR or NUMERIC.
185
     * @param  string $type
186
     * @return string       Returns 'CHAR' if none is supplied or if it's invalid
187
     */
188 7
    protected function getValidMetaType($type = null)
189
    {
190 7
        if (is_null($type) || !in_array($type, $this->grammar->getComparators())) {
191 6
            return 'CHAR';
192
        }
193
194 2
        return strtoupper($type);
195
    }
196
197
    /**
198
     * Wether or not the given operator is invalid.
199
     * @param  string $operator
200
     * @return bool
201
     */
202 8
    protected function invalidOperator($operator)
203
    {
204 8
        return !in_array($operator, $this->grammar->getOperators());
205
    }
206
    
207
    /**
208
     * Prepare the value and operator by applying defaults and validating the operator.
209
     * @param  string  $value
210
     * @param  string  $operator
211
     * @param  bool $useDefault
212
     * @param  bool $termDefault
213
     * @return array
214
     */
215 8
    protected function prepareValueAndOperator($value, $operator, $useDefault = false, $termDefault = false)
216
    {
217 8
        if ($useDefault) {
218 3
            return [$operator, $termDefault ? 'IN' : '='];
219
        }
220
221 6
        if ($this->invalidOperator($operator) && !is_null($value)) {
222 1
            throw new InvalidArgumentException('Illegal operator and value combination.');
223
        }
224
225 5
        return [$value, $operator];
226
    }
227
}
228