Completed
Push — develop ( f03c1d...7ff4c1 )
by Sander
02:01
created

QueriesMeta::extractNestedMetaClosures()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.072

Importance

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

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

181
            $this->whereMeta(/** @scrutinizer ignore-type */ ...$array);
Loading history...
182
        }
183
184
        return $this;
185
    }
186
187
    /**
188
     * Return a valid comparator type, like CHAR or NUMERIC.
189
     * @param  string $type
190
     * @return string       Returns 'CHAR' if none is supplied or if it's invalid
191
     */
192 7
    protected function getValidMetaType($type = null)
193
    {
194 7
        if (is_null($type) || !in_array($type, $this->getGrammar()->getComparators())) {
195 6
            return 'CHAR';
196
        }
197
198 2
        return strtoupper($type);
199
    }
200
201
    protected abstract function setBinding($key, $data);
202
    
203
    protected abstract function getBinding($key);
204
205
    protected abstract function getGrammar();
206
    
207
    protected abstract function appendBinding($key, $data);
208
    
209
    protected abstract function invalidOperator($operator);
210
    
211
    protected abstract function prepareValueAndOperator($value, $operator, $useDefault = false, $termDefault = false);
212
}
213