Completed
Push — master ( fc8c2e...ea6dab )
by Vitaly
02:58
created

Query::id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 9
rs 9.6667
cc 1
eloc 4
nc 1
nop 1
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 condition to current query.
227
     *
228
     * @param string|Condition|Argument $fieldName Entity field name
229
     * @param string $fieldValue Value
230
     * @param string $relation Relation between field name and its value
231
     * @return self Chaining
232
     */
233
    public function where($fieldName, $fieldValue = null, $relation = '=')
234
    {
235
        // If empty array is passed
236
        if (is_string($fieldName)) {
237
            // Handle empty field value passing to avoid unexpected behaviour
238
            if (!isset($fieldValue)) {
239
                $relation = ArgumentInterface::ISNULL;
240
                $fieldValue = '';
241
            }
242
243
            // Add condition argument
244
            $this->getConditionGroup($fieldName)->add($fieldName, $fieldValue, $relation);
245
        } elseif (is_array($fieldValue) && !sizeof($fieldValue)) {
246
            $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...
247
            return $this;
248
        } elseif (is_a($fieldName, __NAMESPACE__.'\Condition')) {
249
            foreach ($fieldName as $argument) {
250
                // If passed condition group has another condition group as argument
251
                if (is_a($fieldName, __NAMESPACE__.'\Condition')) {
252
                    // Go deeper in recursion
253
                    return $this->where($argument, $fieldValue, $relation);
0 ignored issues
show
Bug introduced by
It seems like $fieldValue can also be of type array; however, samsonframework\orm\Query::where() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
254
                } else { // Otherwise add condition argument to correct condition group
255
                    $this->getConditionGroup($argument->field)->addArgument($fieldName);
256
                }
257
            }
258
        } elseif (is_a($fieldName, __NAMESPACE__.'\Argument')) {
259
            $this->getConditionGroup($fieldName->field)->addArgument($fieldName);
260
        }
261
262
        return $this;
263
    }
264
265
    /**
266
     * Join entity to query.
267
     *
268
     * @param string $entityName Entity identifier
269
     * @return self Chaining
270
     */
271
    public function join($entityName)
272
    {
273
        // TODO: We need to implement this logic
274
275
        // Chaining
276
        return $this;
277
    }
278
279
    /**
280
     * Add query result grouping.
281
     *
282
     * @param string $fieldName Entity field identifier for grouping
283
     * @return self Chaining
284
     */
285
    public function groupBy($fieldName)
286
    {
287
        $this->grouping[] = $fieldName;
288
289
        // Chaining
290
        return $this;
291
    }
292
293
    /**
294
     * Add query result quantity limitation.
295
     *
296
     * @param int $offset Resulting offset
297
     * @param null|int $quantity Amount of RecordInterface object to return
298
     * @return self Chaining
299
     */
300
    public function limit($offset, $quantity = null)
301
    {
302
        $this->limitation = array($offset, $quantity);
303
304
        // Chaining
305
        return $this;
306
    }
307
308
    /**
309
     * Add query result sorting.
310
     *
311
     * @param string $fieldName Entity field identifier for worting
312
     * @param string $order Sorting order
313
     * @return self Chaining
314
     */
315
    public function orderBy($fieldName, $order = 'ASC')
316
    {
317
        $this->sorting[] = array($fieldName, $order);
318
319
        // Chaining
320
        return $this;
321
    }
322
323
    /**
324
     * Add condition by primary field
325
     *
326
     * @param string $value Primary field value
327
     * @return self Chaining
328
     */
329
    public function id($value)
330
    {
331
        // PHP 5.2 get primary field
332
        $_primary = null;
333
        eval('$_primary = ' . $this->class_name . '::$_primary;');
334
335
        // Set primary field value
336
        return $this->where($_primary, $value);
337
    }
338
339
    /**
340
     * Add condition to current query.
341
     *
342
     * @param string|Condition|Argument $fieldName Entity field name
343
     * @param string $fieldValue Value
344
     * @param string $relation Relation between field name and its value
345
     * @deprecated @see self::where()
346
     * @return self Chaining
347
     */
348
    public function cond($fieldName, $fieldValue = null, $relation = '=')
349
    {
350
        return $this->where($fieldName, $fieldValue, $relation);
351
    }
352
}
353