Completed
Push — master ( 9d6bbd...fc8c2e )
by Vitaly
03:03
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
     * Reset all query parameters
35
     * @return self Chaining
36
     */
37
    public function flush()
38
    {
39
        // TODO: Do we need it?
40
        foreach ($this->parameters as $param) {
41
            $param->flush();
42
        }
43
44
        return $this;
45
    }
46
47
    /**
48
     * Perform database request and get collection of database record objects
49
     * @see \samson\activerecord\Query::execute()
50
     * @param mixed $return External variable to store query results
51
     * @return mixed If no arguments passed returns query results collection, otherwise query success status
52
     */
53
    public function exec(& $return = null)
54
    {
55
        $args = func_num_args();
56
        return $this->execute($return, $args);
57
    }
58
59
    /**
60
     * Perform database request and get first record from results collection
61
     * @see \samson\activerecord\Query::execute()
62
     * @param mixed $return External variable to store query results
63
     * @return mixed If no arguments passed returns query results first database record object,
64
     * otherwise query success status
65
     */
66
    public function first(& $return = null)
67
    {
68
        $args = func_num_args();
69
        return $this->execute($return, $args, 1);
70
    }
71
72
    /**
73
     * Perform database request and get array of record field values
74
     * @see \samson\activerecord\Query::execute()
75
     * @param string $fieldName Record field name to get value from
76
     * @param string $return External variable to store query results
77
     * @return Ambigous <boolean, NULL, mixed>
78
     */
79
    public function fields($fieldName, & $return = null)
80
    {
81
        // Call handlers stack
82
        $this->_callHandlers();
83
84
        // Perform DB request
85
        $return = db()->fetchColumn($this->class_name, $this, $fieldName);
86
87
        $success = is_array($return) && sizeof($return);
88
89
        // If parent function has arguments - consider them as return value and return request status
90
        if (func_num_args() - 1 > 0) {
91
            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...
92
        } else { // Parent function has no arguments, return request result
93
            return $return;
94
        }
95
    }
96
97
98
    /**
99
     * Perform database request and return different results depending on function arguments.
100
     * @see \samson\activerecord\Record
101
     * @param array $result External variable to store dabatase request results collection
102
     * @param integer|bool $rType Amount of arguments passed to parent function
103
     * @param integer $limit Quantity of records to return
104
     * @param callable $handler External callable handler for results modification
105
     * @param array $handlerArgs External callable handler arguments
106
     * @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...
107
     */
108
    protected function &execute(
109
        & $result = null,
110
        $rType = false,
111
        $limit = null,
112
        $handler = null,
113
        $handlerArgs = array()
114
    )
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...
115
    {
116
        // Call handlers stack
117
        $this->_callHandlers();
118
119
        // Perform DB request
120
        $result = db()->find($this->class_name, $this);
121
122
        // If external result handler is passed - use it
123
        if (isset($handler)) {
124
            // Add results collection to array
125
            array_unshift($handlerArgs, $result);
126
127
            // Call external handler with parameters
128
            $result = call_user_func_array($handler, $handlerArgs);
129
        }
130
131
        // Clear this query
132
        $this->flush();
133
134
        // Count records
135
        $count = sizeof($result);
136
137
        // Define is request was successful
138
        $success = is_array($result) && $count;
139
140
        // Is amount of records is specified
141
        if (isset($limit)) {
142
            // If we have not enought records - return null
143
            if ($count < $limit) {
144
                $result = null;
145
            } elseif ($limit === 1) { // If we need first record
146
                $result = array_shift($result);
147
            } elseif ($limit > 1) { // Slice array for nessesar amount
148
                $result = array_slice($result, 0, $limit);
149
            }
150
        }
151
152
        // If parent function has arguments - consider them as return value and return request status
153
        if ($rType > 0) {
154
            return $success;
155
        } else { // Parent function has no arguments, return request result
156
            return $result;
157
        }
158
    }
159
160
    /**
161
     * Set query entity to work with.
162
     *
163
     * @param string $entity Entity identifier
164
     * @return Query|string Chaining or current entity identifier if nothing is passed
165
     * @throws EntityNotFound
166
     */
167
    public function entity($entity = null)
168
    {
169
        if (func_num_args() > 0) {
170
            if (class_exists($entity)) {
171
                $this->class_name = $entity;
172
            } else {
173
                throw new EntityNotFound('['.$entity.'] not found');
174
            }
175
        }
176
177
        return $this->class_name;
178
    }
179
180
    /**
181
     * Get correct query condition depending on entity field name.
182
     * If base entity has field with this name - use base entity condition
183
     * group, otherwise default condition group.
184
     *
185
     * @param string $fieldName Entity field name
186
     * @return Condition Correct query condition group
187
     */
188
    protected function &getConditionGroup($fieldName)
189
    {
190
        if (property_exists($this->class_name, $fieldName)) {
191
            // Add this condition to base entity condition group
192
            return $this->own_condition;
193
        }
194
195
        return $this->cConditionGroup;
196
    }
197
198
    /**
199
     * Add condition to current query.
200
     *
201
     * @param string|Condition|Argument $fieldName Entity field name
202
     * @param string $fieldValue Value
203
     * @param string $relation Relation between field name and its value
204
     * @return self Chaining
205
     */
206
    public function where($fieldName, $fieldValue = null, $relation = '=')
207
    {
208
        // If empty array is passed
209
        if (is_string($fieldName)) {
210
            // Handle empty field value passing to avoid unexpected behaviour
211
            if (!isset($fieldValue)) {
212
                $relation = ArgumentInterface::ISNULL;
213
                $fieldValue = '';
214
            }
215
216
            // Add condition argument
217
            $this->getConditionGroup($fieldName)->add($fieldName, $fieldValue, $relation);
218
        } elseif (is_array($fieldValue) && !sizeof($fieldValue)) {
219
            $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...
220
            return $this;
221
        } elseif (is_a($fieldName, __NAMESPACE__.'\Condition')) {
222
            foreach ($fieldName as $argument) {
223
                // If passed condition group has another condition group as argument
224
                if (is_a($fieldName, __NAMESPACE__.'\Condition')) {
225
                    // Go deeper in recursion
226
                    return $this->cond($argument, $fieldValue, $relation);
0 ignored issues
show
Bug introduced by
It seems like $fieldValue can also be of type array; however, samsonframework\orm\Query::cond() 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...
Deprecated Code introduced by
The method samsonframework\orm\Query::cond() has been deprecated with message: @see self::where()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
227
                } else { // Otherwise add condition argument to correct condition group
228
                    $this->getConditionGroup($argument->field)->addArgument($fieldName);
229
                }
230
            }
231
        } elseif (is_a($fieldName, __NAMESPACE__.'\Argument')) {
232
            $this->getConditionGroup($fieldName->field)->addArgument($fieldName);
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * Join entity to query.
240
     *
241
     * @param string $entityName Entity identifier
242
     * @return self Chaining
243
     */
244
    public function join($entityName)
245
    {
246
        // TODO: We need to implement this logic
247
248
        // Chaining
249
        return $this;
250
    }
251
252
    /**
253
     * Add query result grouping.
254
     *
255
     * @param string $fieldName Entity field identifier for grouping
256
     * @return self Chaining
257
     */
258
    public function groupBy($fieldName)
259
    {
260
        $this->grouping[] = $fieldName;
261
262
        // Chaining
263
        return $this;
264
    }
265
266
    /**
267
     * Add query result quantity limitation.
268
     *
269
     * @param int $offset Resulting offset
270
     * @param null|int $quantity Amount of RecordInterface object to return
271
     * @return self Chaining
272
     */
273
    public function limit($offset, $quantity = null)
274
    {
275
        $this->limitation = array($offset, $quantity);
276
277
        // Chaining
278
        return $this;
279
    }
280
281
    /**
282
     * Add query result sorting.
283
     *
284
     * @param string $fieldName Entity field identifier for worting
285
     * @param string $order Sorting order
286
     * @return self Chaining
287
     */
288
    public function orderBy($fieldName, $order = 'ASC')
289
    {
290
        $this->sorting[] = array($fieldName, $order);
291
292
        // Chaining
293
        return $this;
294
    }
295
296
    /**
297
     * Add condition by primary field
298
     *
299
     * @param string $value Primary field value
300
     * @return self Chaining
301
     */
302
    public function id($value)
303
    {
304
        // PHP 5.2 get primary field
305
        $_primary = null;
306
        eval('$_primary = ' . $this->class_name . '::$_primary;');
307
308
        // Set primary field value
309
        return $this->where($_primary, $value);
310
    }
311
312
    /**
313
     * Add condition to current query.
314
     *
315
     * @param string|Condition|Argument $fieldName Entity field name
316
     * @param string $fieldValue Value
317
     * @param string $relation Relation between field name and its value
318
     * @deprecated @see self::where()
319
     * @return self Chaining
320
     */
321
    public function cond($fieldName, $fieldValue = null, $relation = '=')
322
    {
323
        return $this->where($fieldName, $fieldValue, $relation);
324
    }
325
}
326