Completed
Push — master ( 821d1c...c9e7f5 )
by Vitaly
04:33
created

Query::cond()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 4
Metric Value
c 5
b 0
f 4
dl 0
loc 16
rs 8.8571
cc 6
eloc 11
nc 5
nop 3
1
<?php
2
namespace samsonframework\orm;
3
use samsonframework\orm\exception\EntityNotFound;
4
5
/**
6
 * Database query builder and executer.
7
 * @author Vitaly Iegorov <[email protected]>
8
 * @version 2.0
9
 */
10
class Query extends QueryHandler implements QueryInterface
11
{
12
    /** @var string Class name for interacting with database */
13
    protected $class_name;
14
15
    /** @var self[] Collection of query parameters objects */
16
    protected $parameters = array();
17
18
    /** @var array Collection of entity field names for sorting order */
19
    protected $sorting = array();
20
21
    /** @var array Collection of entity field names for grouping query results */
22
    protected $grouping = array();
23
24
    /** @var array Collection of query results limitations */
25
    protected $limitation = array();
26
27
    /** @var Condition Query base entity condition group */
28
    protected $own_condition;
29
30
    /** @var Condition Query entity condition group */
31
    protected $cConditionGroup;
32
33
    /**
34
     * Query constructor.
35
     * @param string|null $entity Entity identifier
36
     * @throws EntityNotFound
37
     */
38
    public function __construct($entity = null)
39
    {
40
        $this->entity($entity);
41
        $this->flush();
42
    }
43
44
    /**
45
     * Reset all query parameters
46
     * @return self Chaining
47
     */
48
    public function flush()
49
    {
50
        // TODO: Do we need it?
51
        foreach ($this->parameters as $param) {
52
            $param->flush();
53
        }
54
55
        $this->grouping = array();
56
        $this->limitation = array();
57
        $this->sorting = array();
58
59
        $this->cConditionGroup = new Condition();
60
        $this->own_condition = new Condition();
61
62
        return $this;
63
    }
64
65
    /**
66
     * Perform database request and get collection of database record objects
67
     * @see \samson\activerecord\Query::execute()
68
     * @param mixed $return External variable to store query results
69
     * @return mixed If no arguments passed returns query results collection, otherwise query success status
70
     */
71
    public function exec(& $return = null)
72
    {
73
        $args = func_num_args();
74
        return $this->execute($return, $args);
75
    }
76
77
    /**
78
     * Perform database request and get first record from results collection
79
     * @see \samson\activerecord\Query::execute()
80
     * @param mixed $return External variable to store query results
81
     * @return mixed If no arguments passed returns query results first database record object,
82
     * otherwise query success status
83
     */
84
    public function first(& $return = null)
85
    {
86
        $args = func_num_args();
87
        return $this->execute($return, $args, 1);
88
    }
89
90
    /**
91
     * Perform database request and get array of record field values
92
     * @see \samson\activerecord\Query::execute()
93
     * @param string $fieldName Record field name to get value from
94
     * @param string $return External variable to store query results
95
     * @return Ambigous <boolean, NULL, mixed>
96
     */
97
    public function fields($fieldName, & $return = null)
98
    {
99
        // Call handlers stack
100
        $this->_callHandlers();
101
102
        // Perform DB request
103
        $return = db()->fetchColumn($this->class_name, $this, $fieldName);
104
105
        $success = is_array($return) && sizeof($return);
106
107
        // If parent function has arguments - consider them as return value and return request status
108
        if (func_num_args() - 1 > 0) {
109
            return $success;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $success; (boolean) is incompatible with the return type documented by samsonframework\orm\Query::fields of type samsonframework\orm\Ambigous.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
110
        } else { // Parent function has no arguments, return request result
111
            return $return;
112
        }
113
    }
114
115
116
    /**
117
     * Perform database request and return different results depending on function arguments.
118
     * @see \samson\activerecord\Record
119
     * @param array $result External variable to store dabatase request results collection
120
     * @param integer|bool $rType Amount of arguments passed to parent function
121
     * @param integer $limit Quantity of records to return
122
     * @param callable $handler External callable handler for results modification
123
     * @param array $handlerArgs External callable handler arguments
124
     * @return boolean/array Boolean if $r_type > 0, otherwise array of request results
0 ignored issues
show
Documentation introduced by
The doc-type boolean/array could not be parsed: Unknown type name "boolean/array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
125
     */
126
    protected function &execute(
127
        & $result = null,
128
        $rType = false,
129
        $limit = null,
130
        $handler = null,
131
        $handlerArgs = array()
132
    )
0 ignored issues
show
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found newline
Loading history...
133
    {
134
        // Call handlers stack
135
        $this->_callHandlers();
136
137
        // Perform DB request
138
        $result = db()->find($this->class_name, $this);
139
140
        // If external result handler is passed - use it
141
        if (isset($handler)) {
142
            // Add results collection to array
143
            array_unshift($handlerArgs, $result);
144
145
            // Call external handler with parameters
146
            $result = call_user_func_array($handler, $handlerArgs);
147
        }
148
149
        // Clear this query
150
        $this->flush();
151
152
        // Count records
153
        $count = sizeof($result);
154
155
        // Define is request was successful
156
        $success = is_array($result) && $count;
157
158
        // Is amount of records is specified
159
        if (isset($limit)) {
160
            // If we have not enought records - return null
161
            if ($count < $limit) {
162
                $result = null;
163
            } elseif ($limit === 1) { // If we need first record
164
                $result = array_shift($result);
165
            } elseif ($limit > 1) { // Slice array for nessesar amount
166
                $result = array_slice($result, 0, $limit);
167
            }
168
        }
169
170
        // If parent function has arguments - consider them as return value and return request status
171
        if ($rType > 0) {
172
            return $success;
173
        } else { // Parent function has no arguments, return request result
174
            return $result;
175
        }
176
    }
177
178
    /**
179
     * Set query entity to work with.
180
     *
181
     * @param string $entity Entity identifier
182
     * @return Query|string Chaining or current entity identifier if nothing is passed
183
     * @throws EntityNotFound
184
     */
185
    public function entity($entity = null)
186
    {
187
        if (func_num_args() > 0) {
188
            // Old support for not full class names
189
            if (strpos($entity, '\\') === false) {
190
                // Add generic namespace
191
                $entity = '\samson\activerecord\\'.$entity;
192
            }
193
194
            if (class_exists($entity)) {
195
                $this->flush();
196
                $this->class_name = $entity;
197
            } else {
198
                throw new EntityNotFound('['.$entity.'] not found');
199
            }
200
201
            return $this;
202
        }
203
204
        return $this->class_name;
205
    }
206
207
    /**
208
     * Get correct query condition depending on entity field name.
209
     * If base entity has field with this name - use base entity condition
210
     * group, otherwise default condition group.
211
     *
212
     * @param string $fieldName Entity field name
213
     * @return Condition Correct query condition group
214
     */
215
    protected function &getConditionGroup($fieldName)
216
    {
217
        if (property_exists($this->class_name, $fieldName)) {
218
            // Add this condition to base entity condition group
219
            return $this->own_condition;
220
        }
221
222
        return $this->cConditionGroup;
223
    }
224
225
    /**
226
     * Add query condition as prepared Condition instance.
227
     *
228
     * @param Condition $condition Condition to be added
229
     * @return self Chaining
230
     */
231
    public function whereCondition(Condition $condition)
232
    {
233
        // Iterate condition arguments
234
        foreach ($condition as $argument) {
235
            // If passed condition group has another condition group as argument
236
            if (is_a($argument, __NAMESPACE__ . '\Condition')) {
237
                // Go deeper in recursion
238
                $this->whereCondition($argument);
239
            } else { // Otherwise add condition argument to correct condition group
240
                $this->getConditionGroup($argument->field)->addArgument($argument);
241
            }
242
        }
243
244
        return $this;
245
    }
246
247
    /**
248
     * Add condition to current query.
249
     *
250
     * @param string|Condition|Argument $fieldName Entity field name
251
     * @param string $fieldValue Value
252
     * @param string $relation Relation between field name and its value
253
     * @return self Chaining
254
     */
255
    public function where($fieldName, $fieldValue = null, $relation = '=')
256
    {
257
        // If empty array is passed
258
        if (is_string($fieldName)) {
259
            // Handle empty field value passing to avoid unexpected behaviour
260
            if (!isset($fieldValue)) {
261
                $relation = ArgumentInterface::ISNULL;
262
                $fieldValue = '';
263
            }
264
265
            // Add condition argument
266
            $this->getConditionGroup($fieldName)->add($fieldName, $fieldValue, $relation);
267
        } else {
268
            throw new \InvalidArgumentException('You can only pass string as first argument');
269
        }
270
271
        return $this;
272
    }
273
274
    /**
275
     * Join entity to query.
276
     *
277
     * @param string $entityName Entity identifier
278
     * @return self Chaining
279
     */
280
    public function join($entityName)
281
    {
282
        // TODO: We need to implement this logic
283
        $entityName = '';
0 ignored issues
show
Unused Code introduced by
$entityName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
284
285
        // Chaining
286
        return $this;
287
    }
288
289
    /**
290
     * Add query result grouping.
291
     *
292
     * @param string $fieldName Entity field identifier for grouping
293
     * @return self Chaining
294
     */
295
    public function groupBy($fieldName)
296
    {
297
        $this->grouping[] = $fieldName;
298
299
        // Chaining
300
        return $this;
301
    }
302
303
    /**
304
     * Add query result quantity limitation.
305
     *
306
     * @param int $offset Resulting offset
307
     * @param null|int $quantity Amount of RecordInterface object to return
308
     * @return self Chaining
309
     */
310
    public function limit($offset, $quantity = null)
311
    {
312
        $this->limitation = array($offset, $quantity);
313
314
        // Chaining
315
        return $this;
316
    }
317
318
    /**
319
     * Add query result sorting.
320
     *
321
     * @param string $fieldName Entity field identifier for worting
322
     * @param string $order Sorting order
323
     * @return self Chaining
324
     */
325
    public function orderBy($fieldName, $order = 'ASC')
326
    {
327
        $this->sorting[] = array($fieldName, $order);
328
329
        // Chaining
330
        return $this;
331
    }
332
333
    /**
334
     * Add condition by primary field
335
     *
336
     * @param string $value Primary field value
337
     * @return self Chaining
338
     */
339
    public function id($value)
340
    {
341
        // PHP 5.2 get primary field
342
        $_primary = null;
343
        eval('$_primary = ' . $this->class_name . '::$_primary;');
344
345
        // Set primary field value
346
        return $this->where($_primary, $value);
347
    }
348
349
    /**
350
     * Add condition to current query.
351
     * This method supports receives three possible types for $fieldName,
352
     * this is deprecated logic and this should be changed to use separate methods
353
     * for each argument type.
354
     *
355
     * @param string|Condition|Argument $fieldName Entity field name
356
     * @param string $fieldValue Value
357
     * @param string $relation Relation between field name and its value
358
     * @deprecated @see self::where()
359
     * @return self Chaining
360
     */
361
    public function cond($fieldName, $fieldValue = null, $relation = '=')
362
    {
363
        // If empty array is passed
364
        if (is_string($fieldName)) {
365
            return $this->where($fieldName, $fieldValue, $relation);
366
        } elseif (is_array($fieldValue) && !sizeof($fieldValue)) {
367
            $this->empty = true;
0 ignored issues
show
Bug introduced by
The property empty does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
368
            return $this;
369
        } elseif (is_a($fieldName, __NAMESPACE__.'\Condition')) {
370
            $this->whereCondition($fieldName);
371
        } elseif (is_a($fieldName, __NAMESPACE__.'\Argument')) {
372
            $this->getConditionGroup($fieldName->field)->addArgument($fieldName);
373
        }
374
375
        return $this;
376
    }
377
}
378