Completed
Push — master ( 208ab7...00f3be )
by Vitaly
02:53
created

Query::cond()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 3
Metric Value
c 3
b 0
f 3
dl 0
loc 31
rs 4.9091
cc 9
eloc 18
nc 7
nop 3
1
<?php
2
namespace samsonframework\orm;
3
4
/**
5
 * Database query builder and executer.
6
 * @author Vitaly Iegorov <[email protected]>
7
 * @version 2.0
8
 */
9
class Query extends QueryHandler implements QueryInterface
10
{
11
    /** @var string Class name for interacting with database */
12
    protected $class_name;
13
14
    /** @var self[] Collection of query parameters objects */
15
    protected $parameters = array();
16
17
    /** @var array Collection of entity field names for sorting order */
18
    protected $sorting = array();
19
20
    /** @var array Collection of entity field names for grouping query results */
21
    protected $grouping = array();
22
23
    /** @var array Collection of query results limitations */
24
    protected $limitation = array();
25
26
    /** @var Condition Query base entity condition group */
27
    protected $own_condition;
28
29
    /** @var Condition Query entity condition group */
30
    protected $cConditionGroup;
31
32
    /**
33
     * Reset all query parameters
34
     * @return self Chaining
35
     */
36
    public function flush()
37
    {
38
        // TODO: Do we need it?
39
        foreach ($this->parameters as $param) {
40
            $param->flush();
41
        }
42
43
        return $this;
44
    }
45
46
    /**
47
     * Perform database request and get collection of database record objects
48
     * @see \samson\activerecord\Query::execute()
49
     * @param mixed $return External variable to store query results
50
     * @return mixed If no arguments passed returns query results collection, otherwise query success status
51
     */
52
    public function exec(& $return = null)
53
    {
54
        $args = func_num_args();
55
        return $this->execute($return, $args);
56
    }
57
58
    /**
59
     * Perform database request and get first record from results collection
60
     * @see \samson\activerecord\Query::execute()
61
     * @param mixed $return External variable to store query results
62
     * @return mixed If no arguments passed returns query results first database record object,
63
     * otherwise query success status
64
     */
65
    public function first(& $return = null)
66
    {
67
        $args = func_num_args();
68
        return $this->execute($return, $args, 1);
69
    }
70
71
    /**
72
     * Perform database request and get array of record field values
73
     * @see \samson\activerecord\Query::execute()
74
     * @param string $fieldName Record field name to get value from
75
     * @param string $return External variable to store query results
76
     * @return Ambigous <boolean, NULL, mixed>
77
     */
78
    public function fields($fieldName, & $return = null)
79
    {
80
        // Call handlers stack
81
        $this->_callHandlers();
82
83
        // Perform DB request
84
        $return = db()->fetchColumn($this->class_name, $this, $fieldName);
85
86
        $success = is_array($return) && sizeof($return);
87
88
        // If parent function has arguments - consider them as return value and return request status
89
        if (func_num_args() - 1 > 0) {
90
            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...
91
        } else { // Parent function has no arguments, return request result
92
            return $return;
93
        }
94
    }
95
96
97
    /**
98
     * Perform database request and return different results depending on function arguments.
99
     * @see \samson\activerecord\Record
100
     * @param array $result External variable to store dabatase request results collection
101
     * @param integer|bool $rType Amount of arguments passed to parent function
102
     * @param integer $limit Quantity of records to return
103
     * @param callable $handler External callable handler for results modification
104
     * @param array $handlerArgs External callable handler arguments
105
     * @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...
106
     */
107
    protected function &execute(
108
        & $result = null,
109
        $rType = false,
110
        $limit = null,
111
        $handler = null,
112
        $handlerArgs = array()
113
    )
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...
114
    {
115
        // Call handlers stack
116
        $this->_callHandlers();
117
118
        // Perform DB request
119
        $result = db()->find($this->class_name, $this);
120
121
        // If external result handler is passed - use it
122
        if (isset($handler)) {
123
            // Add results collection to array
124
            array_unshift($handlerArgs, $result);
125
126
            // Call external handler with parameters
127
            $result = call_user_func_array($handler, $handlerArgs);
128
        }
129
130
        // Clear this query
131
        $this->flush();
132
133
        // Count records
134
        $count = sizeof($result);
135
136
        // Define is request was successful
137
        $success = is_array($result) && $count;
138
139
        // Is amount of records is specified
140
        if (isset($limit)) {
141
            // If we have not enought records - return null
142
            if ($count < $limit) {
143
                $result = null;
144
            } elseif ($limit === 1) { // If we need first record
145
                $result = array_shift($result);
146
            } elseif ($limit > 1) { // Slice array for nessesar amount
147
                $result = array_slice($result, 0, $limit);
148
            }
149
        }
150
151
        // If parent function has arguments - consider them as return value and return request status
152
        if ($rType > 0) {
153
            return $success;
154
        } else { // Parent function has no arguments, return request result
155
            return $result;
156
        }
157
    }
158
159
    /**
160
     * Set query entity to work with.
161
     *
162
     * @param string $entity Entity identifier
163
     * @return self|string Chaining or current entity identifier if nothing is passed
164
     */
165
    public function entity($entity = null)
166
    {
167
        $this->class_name = isset($entity) ? $entity : $this->class_name;
168
169
        return func_num_args() > 0 ? $this->class_name : $this;
170
    }
171
172
    /**
173
     * Get correct query condition depending on entity field name.
174
     * If base entity has field with this name - use base entity condition
175
     * group, otherwise default condition group.
176
     *
177
     * @param string $fieldName Entity field name
178
     * @return Condition Correct query condition group
179
     */
180
    protected function &getConditionGroup($fieldName)
181
    {
182
        if (property_exists($this->class_name, $fieldName)) {
183
            // Add this condition to base entity condition group
184
            return $this->own_condition;
185
        }
186
187
        return $this->cConditionGroup;
188
    }
189
190
    /**
191
     * Add condition to current query.
192
     *
193
     * @param string|Condition|Argument $fieldName Entity field name
194
     * @param string $fieldValue Value
195
     * @param string $relation Relation between field name and its value
196
     * @return self Chaining
197
     */
198
    public function cond($fieldName, $fieldValue = null, $relation = '=')
199
    {
200
        // If empty array is passed
201
        if (is_string($fieldName)) {
202
            // Handle empty field value passing to avoid unexpected behaviour
203
            if (!isset($fieldValue)) {
204
                $relation = ArgumentInterface::ISNULL;
205
                $fieldValue = '';
206
            }
207
208
            // Add condition argument
209
            $this->getConditionGroup($fieldName)->add($fieldName, $fieldValue, $relation);
210
        } elseif (is_array($fieldValue) && !sizeof($fieldValue)) {
211
            $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...
212
            return $this;
213
        } elseif (is_a($fieldName, __NAMESPACE__.'\Condition')) {
214
            foreach ($fieldName as $argument) {
215
                // If passed condition group has another condition group as argument
216
                if (is_a($fieldName, __NAMESPACE__.'\Condition')) {
217
                    // Go deeper in recursion
218
                    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...
219
                } else { // Otherwise add condition argument to correct condition group
220
                    $this->getConditionGroup($argument->field)->addArgument($fieldName);
221
                }
222
            }
223
        } elseif (is_a($fieldName, __NAMESPACE__.'\Argument')) {
224
            $this->getConditionGroup($fieldName->field)->addArgument($fieldName);
225
        }
226
227
        return $this;
228
    }
229
230
    /**
231
     * Join entity to query.
232
     *
233
     * @param string $entityName Entity identifier
234
     * @return self Chaining
235
     */
236
    public function join($entityName)
237
    {
238
        // TODO: We need to implement this logic
239
240
        // Chaining
241
        return $this;
242
    }
243
244
    /**
245
     * Add query result grouping.
246
     *
247
     * @param string $fieldName Entity field identifier for grouping
248
     * @return self Chaining
249
     */
250
    public function groupBy($fieldName)
251
    {
252
        $this->grouping[] = $fieldName;
253
254
        // Chaining
255
        return $this;
256
    }
257
258
    /**
259
     * Add query result quantity limitation.
260
     *
261
     * @param int $offset Resulting offset
262
     * @param null|int $quantity Amount of RecordInterface object to return
263
     * @return self Chaining
264
     */
265
    public function limit($offset, $quantity = null)
266
    {
267
        $this->limitation = array($offset, $quantity);
268
269
        // Chaining
270
        return $this;
271
    }
272
273
    /**
274
     * Add query result sorting.
275
     *
276
     * @param string $fieldName Entity field identifier for worting
277
     * @param string $order Sorting order
278
     * @return self Chaining
279
     */
280
    public function orderBy($fieldName, $order = 'ASC')
281
    {
282
        $this->sorting[] = array($fieldName, $order);
283
284
        // Chaining
285
        return $this;
286
    }
287
}
288