Completed
Push — master ( 882132...509951 )
by James Ekow Abaka
02:54
created

QueryOperations::buildFetchQueryParameters()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 8.8571
cc 5
eloc 12
nc 4
nop 2
crap 5
1
<?php
2
3
/*
4
 * The MIT License
5
 *
6
 * Copyright 2015 ekow.
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
namespace ntentan\nibii;
28
29
use ntentan\atiaa\Driver;
30
use ntentan\nibii\exceptions\ModelNotFoundException;
31
use ntentan\nibii\exceptions\NibiiException;
32
use ntentan\utils\Text;
33
34
/**
35
 * Performs operations that query the database.
36
 *
37
 * @package ntentan\nibii
38
 */
39
class QueryOperations
40
{
41
42
    /**
43
     * An instance of the record wrapper being used.
44
     *
45
     * @var RecordWrapper
46
     */
47
    private $wrapper;
48
49
    /**
50
     * An instance of the driver adapter used in the database connection.
51
     *
52
     * @var DriverAdapter
53
     */
54
    private $adapter;
55
56
    /**
57
     * An instance of query parameters used to perform the various queries.
58
     *
59
     * @var QueryParameters
60
     */
61
    private $queryParameters;
62
63
    /**
64
     * The name of a method initialized through a dynamic method waiting to be executed.
65
     *
66
     * @var string
67
     */
68
    private $pendingMethod;
69
70
    /**
71
     * Regular expressions for matching dynamic methods.
72
     *
73
     * @var array
74
     */
75
    private $dynamicMethods = [
76
        "/(?<method>filterBy)(?<variable>[A-Z][A-Za-z]+){1}/",
77
        "/(?<method>sort)(?<direction>Asc|Desc)?(By)(?<variable>[A-Z][A-Za-z]+){1}/",
78
        "/(?<method>fetch)(?<first>First)?(With)(?<variable>[A-Za-z]+)/"
79
    ];
80
81
    /**
82
     * An instance of the DataOperations class used for filtered deletes.
83
     *
84
     * @var DataOperations
85
     */
86
    private $dataOperations;
87
88
    /**
89
     * An instance of the Driver class used for establishing database connections.
90
     *
91
     * @var Driver
92
     */
93
    private $driver;
94
95
    /**
96
     * QueryOperations constructor
97
     *
98
     * @param RecordWrapper $wrapper
99
     * @param DataOperations $dataOperations
100
     * @param Driver $driver
101
     * @internal param DriverAdapter $adapter
102
     */
103 34
    public function __construct(RecordWrapper $wrapper, DataOperations $dataOperations, Driver $driver)
104
    {
105 34
        $this->wrapper = $wrapper;
106 34
        $this->adapter = $wrapper->getAdapter();
107 34
        $this->dataOperations = $dataOperations;
108 34
        $this->driver = $driver;
109 34
    }
110
111
    /**
112
     * Fetches items from the database.
113
     *
114
     * @param int|array|QueryParameters $query
115
     * @return RecordWrapper
116
     */
117 24
    public function doFetch($query = null)
118
    {
119 24
        $parameters = $this->buildFetchQueryParameters($query);
120 24
        $data = $this->adapter->select($parameters);
121 24
        $this->wrapper->setData($data);
122 24
        $this->resetQueryParameters();
123 24
        return $this->wrapper;
124
    }
125
126
    /**
127
     * The method takes multiple types of arguments and converts it to a QueryParametersObject.
128
     * When this method receives null, it returns a new instance of QueryParameters. When it receives an integer, it
129
     * returns a QueryParameters object that points the primary key to the integer. When it receives an associative
130
     * array, it builds a series of conditions with array key-value pairs.
131
     *
132
     * @param int|array|QueryParameters $arg
133
     * @param bool $instantiate
134
     * @return QueryParameters
135
     */
136 26
    private function buildFetchQueryParameters($arg, $instantiate = true)
137
    {
138 26
        if ($arg instanceof QueryParameters) {
139 12
            return $arg;
140
        }
141
142 26
        $parameters = $this->getQueryParameters($instantiate);
143
144 26
        if (is_numeric($arg)) {
145 6
            $description = $this->wrapper->getDescription();
146 6
            $parameters->addFilter($description->getPrimaryKey()[0], $arg);
147 6
            $parameters->setFirstOnly(true);
148 22
        } else if (is_array($arg)) {
149 6
            foreach ($arg as $field => $value) {
150 6
                $parameters->addFilter($field, $value);
151
            }
152
        }
153
154 26
        return $parameters;
155
    }
156
157
    /**
158
     * Creates a new instance of the QueryParameters if required or just returns an already instance.
159
     *
160
     * @param bool $forceInstantiation
161
     * @return QueryParameters
162
     */
163 32
    private function getQueryParameters($forceInstantiation = true)
164
    {
165 32
        if ($this->queryParameters === null && $forceInstantiation) {
166 32
            $this->queryParameters = new QueryParameters($this->wrapper->getDBStoreInformation()['quoted_table']);
167
        }
168 32
        return $this->queryParameters;
169
    }
170
171
    /**
172
     * Clears up the query parameters.
173
     */
174 32
    private function resetQueryParameters()
175
    {
176 32
        $this->queryParameters = null;
177 32
    }
178
179
    /**
180
     * Performs the fetch operation and returns just the first item.
181
     *
182
     * @param mixed $id
183
     * @return RecordWrapper
184
     */
185 10
    public function doFetchFirst($id = null)
186
    {
187 10
        $this->getQueryParameters()->setFirstOnly(true);
188 10
        return $this->doFetch($id);
189
    }
190
191
    /**
192
     * Set the fields that should be returned for each record.
193
     *
194
     * @return RecordWrapper
195
     */
196 12
    public function doFields()
197
    {
198 12
        $fields = [];
199 12
        $arguments = func_get_args();
200 12
        foreach ($arguments as $argument) {
201 12
            if (is_array($argument)) {
202 6
                $fields = array_merge($fields, $argument);
203
            } else {
204 12
                $fields[] = $argument;
205
            }
206
        }
207 12
        $this->getQueryParameters()->setFields($fields);
208 12
        return $this->wrapper;
209
    }
210
211
    /**
212
     * Sort the query by a given field in a given directory.
213
     *
214
     * @param string $field
215
     * @param string $direction
216
     */
217
    public function doSortBy($field, $direction = 'ASC')
218
    {
219
        $this->getQueryParameters()->addSort($field, $direction);
220
    }
221
222
    /**
223
     *
224
     *
225
     * @param mixed $arguments
226
     * @return array
227
     */
228 10
    private function getFilter($arguments)
229
    {
230 10
        if (count($arguments) == 2 && is_array($arguments[1])) {
231 2
            $filter = $arguments[0];
232 2
            $data = $arguments[1];
233
        } else {
234 10
            $filter = array_shift($arguments);
235 10
            $data = $arguments;
236
        }
237 10
        return ['filter' => $filter, 'data' => $data];
238
    }
239
240 6
    public function doFilter()
241
    {
242 6
        $arguments = func_get_args();
243 6
        if(count($arguments) == 1 && is_array($arguments[0])) {
244
            foreach($arguments[0] as $field => $value) {
245
                $this->getQueryParameters()->addFilter($field, $value);
246
            }
247
        } else {
248 6
            $details = $this->getFilter($arguments);
249 6
            $this->getQueryParameters()->setFilter($details['filter'], $details['data']);
250
        }
251 6
        return $this->wrapper;
252
    }
253
254 4
    public function doFilterBy()
255
    {
256 4
        $arguments = func_get_args();
257 4
        $details = $this->getFilter($arguments);
258 4
        $this->getQueryParameters()->addFilter($details['filter'], $details['data']);
259 4
        return $this->wrapper;
260
    }
261
262 6
    public function doUpdate($data)
263
    {
264 6
        $this->driver->beginTransaction();
265 6
        $parameters = $this->getQueryParameters();
266 6
        $this->adapter->bulkUpdate($data, $parameters);
267 6
        $this->driver->commit();
268 6
        $this->resetQueryParameters();
269 6
    }
270
271 2
    public function doDelete($args = null)
272
    {
273 2
        $this->driver->beginTransaction();
274 2
        $parameters = $this->buildFetchQueryParameters($args);
275
276 2
        if ($parameters === null) {
277
            $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
278
            $parameters = $this->getQueryParameters();
279
            $data = $this->wrapper->getData();
280
            $keys = [];
281
282
            foreach ($data as $datum) {
283
                if ($this->dataOperations->isItemDeletable($primaryKey, $datum)) {
0 ignored issues
show
Documentation introduced by
$primaryKey is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
                    $keys[] = $datum[$primaryKey[0]];
285
                }
286
            }
287
288
            $parameters->addFilter($primaryKey[0], $keys);
289
            $this->adapter->delete($parameters);
290
        } else {
291 2
            $this->adapter->delete($parameters);
292
        }
293
294 2
        $this->driver->commit();
295 2
        $this->resetQueryParameters();
296 2
    }
297
298 10
    public function runDynamicMethod($arguments)
299
    {
300 10
        $arguments = count($arguments) > 1 ? $arguments : $arguments[0];
301 10
        switch ($this->pendingMethod['method']) {
302 10
            case 'filterBy':
303 4
                $this->getQueryParameters()->addFilter(Text::deCamelize($this->pendingMethod['variable']), $arguments);
304 4
                return $this->wrapper;
305 8
            case 'sort':
306
                $this->getQueryParameters()->addSort(Text::deCamelize($this->pendingMethod['variable']), $this->pendingMethod['direction']);
307
                return $this->wrapper;
308 8
            case 'fetch':
309 8
                $parameters = $this->getQueryParameters();
310 8
                $parameters->addFilter(Text::deCamelize($this->pendingMethod['variable']), $arguments);
311 8
                if ($this->pendingMethod['first'] === 'First') {
312 8
                    $parameters->setFirstOnly(true);
313
                }
314 8
                return $this->doFetch();
315
        }
316
    }
317
318 10
    public function initDynamicMethod($method)
319
    {
320 10
        $return = false;
321
322 10
        foreach ($this->dynamicMethods as $regexp) {
323 10
            if (preg_match($regexp, $method, $matches)) {
324 10
                $return = true;
325 10
                $this->pendingMethod = $matches;
0 ignored issues
show
Documentation Bug introduced by
It seems like $matches of type array<integer,string> is incompatible with the declared type string of property $pendingMethod.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
326 10
                break;
327
            }
328
        }
329
330 10
        return $return;
331
    }
332
333
    public function doCount($query = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
334
    {
335
        return $this->adapter->count($this->buildFetchQueryParameters($query));
336
    }
337
338
    public function doLimit($numItems)
339
    {
340
        $this->getQueryParameters()->setLimit($numItems);
341
        return $this->wrapper;
342
    }
343
344
    public function doOffset($offset)
345
    {
346
        $this->getQueryParameters()->setOffset($offset);
347
        return $this->wrapper;
348
    }
349
350
    public function doWith($model)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
351
    {
352
        if(!isset($this->wrapper->getDescription()->getRelationships()[$model])) {
353
            throw new ModelNotFoundException("Could not find related model [$model]");
354
        }
355
        $relationship = $this->wrapper->getDescription()->getRelationships()[$model];
356
        return $relationship->getQuery();
357
    }
358
359
}
360