Complex classes like Criteria often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Criteria, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class Criteria implements CriteriaInterface |
||
47 | { |
||
48 | |||
49 | use Traits\Criteria\CursorAwareTrait, |
||
50 | Traits\Criteria\DecoratableTrait, |
||
51 | Traits\Criteria\LimitableTrait, |
||
52 | Traits\Criteria\SelectableTrait, |
||
53 | Traits\Criteria\SortableTrait; |
||
54 | |||
55 | /** |
||
56 | * @since v1.0 |
||
57 | * @var array $operators supported operators lists |
||
58 | */ |
||
59 | public static $operators = [ |
||
60 | |||
61 | // Comparison |
||
62 | // Matches values that are equal to a specified value. |
||
63 | 'eq' => '$eq', |
||
64 | 'equals' => '$eq', |
||
65 | '==' => '$eq', |
||
66 | // Matches values that are greater than a specified value. |
||
67 | 'gt' => '$gt', |
||
68 | 'greater' => '$gt', |
||
69 | '>' => '$gt', |
||
70 | // Matches values that are greater than or equal to a specified value. |
||
71 | 'gte' => '$gte', |
||
72 | 'greatereq' => '$gte', |
||
73 | '>=' => '$gte', |
||
74 | // Matches values that are less than a specified value. |
||
75 | 'lt' => '$lt', |
||
76 | 'less' => '$lt', |
||
77 | '<' => '$lt', |
||
78 | // Matches values that are less than or equal to a specified value. |
||
79 | 'lte' => '$lte', |
||
80 | 'lesseq' => '$lte', |
||
81 | '<=' => '$lte', |
||
82 | // Matches all values that are not equal to a specified value. |
||
83 | 'ne' => '$ne', |
||
84 | 'noteq' => '$ne', |
||
85 | '!=' => '$ne', |
||
86 | '<>' => '$ne', |
||
87 | // Matches any of the values specified in an array. |
||
88 | 'in' => '$in', |
||
89 | // Matches none of the values specified in an array. |
||
90 | 'notin' => '$nin', |
||
91 | // Logical |
||
92 | // Joins query clauses with a logical OR returns all documents that match the conditions of either clause. |
||
93 | 'or' => '$or', |
||
94 | // Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. |
||
95 | 'and' => '$and', |
||
96 | // Inverts the effect of a query expression and returns documents that do not match the query expression. |
||
97 | 'not' => '$not', |
||
98 | // Joins query clauses with a logical NOR returns all documents that fail to match both clauses. |
||
99 | 'nor' => '$nor', |
||
100 | // Element |
||
101 | // Matches documents that have the specified field. |
||
102 | 'exists' => '$exists', |
||
103 | 'notexists' => '$exists', |
||
104 | // Selects documents if a field is of the specified type. |
||
105 | 'type' => '$type', |
||
106 | // Evaluation |
||
107 | // Performs a modulo operation on the value of a field and selects documents with a specified result. |
||
108 | 'mod' => '$mod', |
||
109 | '%' => '$mod', |
||
110 | // Selects documents where values match a specified regular expression. |
||
111 | 'regex' => '$regex', |
||
112 | // Performs text search. |
||
113 | 'text' => '$text', |
||
114 | // Matches documents that satisfy a JavaScript expression. |
||
115 | 'where' => '$where', |
||
116 | // Geospatial |
||
117 | // Selects geometries within a bounding GeoJSON geometry. The `2dsphere` and `2d` indexes support $geoWithin. |
||
118 | 'geoWithin' => '$geoWithin', |
||
119 | // Selects geometries that intersect with a GeoJSON geometry. The `2dsphere` index supports $geoIntersects. |
||
120 | 'geoIntersects' => '$geoIntersects', |
||
121 | // Returns geospatial objects in proximity to a point. Requires a geospatial index. The `2dsphere` and `2d` indexes support $near. |
||
122 | 'near' => '$near', |
||
123 | // Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The `2dsphere` and `2d` indexes support $nearSphere. |
||
124 | 'nearSphere' => '$nearSphere', |
||
125 | // Array |
||
126 | // Matches arrays that contain all elements specified in the query. |
||
127 | 'all' => '$all', |
||
128 | // Selects documents if element in the array field matches all the specified $elemMatch conditions. |
||
129 | 'elemmatch' => '$elemMatch', |
||
130 | // Selects documents if the array field is a specified size. |
||
131 | 'size' => '$size', |
||
132 | // Comments |
||
133 | 'comment' => '$comment' |
||
134 | ]; |
||
135 | |||
136 | /** |
||
137 | * Sort Ascending |
||
138 | */ |
||
139 | const SortAsc = SortInterface::SortAsc; |
||
140 | |||
141 | /** |
||
142 | * Sort Descending |
||
143 | */ |
||
144 | const SortDesc = SortInterface::SortDesc; |
||
145 | |||
146 | /** |
||
147 | * Sort Ascending |
||
148 | * @deprecated since version 4.0.7 |
||
149 | */ |
||
150 | const SORT_ASC = SortInterface::SortAsc; |
||
151 | |||
152 | /** |
||
153 | * Sort Descending |
||
154 | * @deprecated since version 4.0.7 |
||
155 | */ |
||
156 | const SORT_DESC = SortInterface::SortDesc; |
||
157 | |||
158 | private $_conditions = []; |
||
159 | |||
160 | /** |
||
161 | * Raw conditions array |
||
162 | * @var mixed[] |
||
163 | */ |
||
164 | private $_rawConds = []; |
||
165 | private $_workingFields = []; |
||
166 | |||
167 | /** |
||
168 | * Constructor |
||
169 | * Example criteria: |
||
170 | * |
||
171 | * <PRE> |
||
172 | * 'criteria' = array( |
||
173 | * 'conditions'=>array( |
||
174 | * 'fieldName1'=>array('greater' => 0), |
||
175 | * 'fieldName2'=>array('>=' => 10), |
||
176 | * 'fieldName3'=>array('<' => 10), |
||
177 | * 'fieldName4'=>array('lessEq' => 10), |
||
178 | * 'fieldName5'=>array('notEq' => 10), |
||
179 | * 'fieldName6'=>array('in' => array(10, 9)), |
||
180 | * 'fieldName7'=>array('notIn' => array(10, 9)), |
||
181 | * 'fieldName8'=>array('all' => array(10, 9)), |
||
182 | * 'fieldName9'=>array('size' => 10), |
||
183 | * 'fieldName10'=>array('exists'), |
||
184 | * 'fieldName11'=>array('notExists'), |
||
185 | * 'fieldName12'=>array('mod' => array(10, 9)), |
||
186 | * 'fieldName13'=>array('==' => 1) |
||
187 | * ), |
||
188 | * 'select'=>array('fieldName', 'fieldName2'), |
||
189 | * 'limit'=>10, |
||
190 | * 'offset'=>20, |
||
191 | * 'sort'=>array('fieldName1'=>Criteria::SortAsc, 'fieldName2'=>Criteria::SortDesc), |
||
192 | * ); |
||
193 | * </PRE> |
||
194 | * @param mixed|CriteriaInterface|Conditions $criteria |
||
195 | * @param AnnotatedInterface|null Model to use for criteria decoration |
||
196 | * @since v1.0 |
||
197 | */ |
||
198 | 86 | public function __construct($criteria = null, AnnotatedInterface $model = null) |
|
199 | { |
||
200 | 86 | $this->setCd(new ConditionDecorator($model)); |
|
201 | 86 | if (is_array($criteria)) |
|
202 | { |
||
203 | 1 | if (isset($criteria['conditions'])) |
|
204 | foreach ($criteria['conditions'] as $fieldName => $conditions) |
||
205 | { |
||
206 | $fieldNameArray = explode('.', $fieldName); |
||
207 | if (count($fieldNameArray) === 1) |
||
208 | { |
||
209 | $fieldName = array_shift($fieldNameArray); |
||
210 | } |
||
211 | else |
||
212 | { |
||
213 | $fieldName = array_pop($fieldNameArray); |
||
214 | } |
||
215 | |||
216 | foreach ($conditions as $operator => $value) |
||
217 | { |
||
218 | $this->setWorkingFields($fieldNameArray); |
||
|
|||
219 | $operator = strtolower($operator); |
||
220 | $this->addCond($fieldName, $operator, $value); |
||
221 | } |
||
222 | } |
||
223 | |||
224 | 1 | if (isset($criteria['select'])) |
|
225 | { |
||
226 | $this->select($criteria['select']); |
||
227 | } |
||
228 | 1 | if (isset($criteria['limit'])) |
|
229 | { |
||
230 | $this->limit($criteria['limit']); |
||
231 | } |
||
232 | 1 | if (isset($criteria['offset'])) |
|
233 | { |
||
234 | $this->offset($criteria['offset']); |
||
235 | } |
||
236 | 1 | if (isset($criteria['sort'])) |
|
237 | { |
||
238 | $this->setSort($criteria['sort']); |
||
239 | } |
||
240 | 1 | if (isset($criteria['useCursor'])) |
|
241 | { |
||
242 | 1 | $this->setUseCursor($criteria['useCursor']); |
|
243 | } |
||
244 | } |
||
245 | // NOTE: |
||
246 | //Scrunitizer: $criteria is of type object<Maslosoft\Mangan\...ria\MergeableInterface>, but the function expects a array|object<Maslosoft\M...aces\CriteriaInterface>. |
||
247 | // But for now it should be this way to easyli distinguish from Conditions. |
||
248 | // Future plan: Use CriteriaInterface here, and drop `$criteria instanceof Conditions` if clause. Conditions should implement CriteriaInterface too. |
||
249 | elseif ($criteria instanceof MergeableInterface) |
||
250 | { |
||
251 | $this->mergeWith($criteria); |
||
252 | } |
||
253 | elseif ($criteria instanceof Conditions) |
||
254 | { |
||
255 | $this->setConditions($criteria); |
||
256 | } |
||
257 | 86 | } |
|
258 | |||
259 | /** |
||
260 | * Merge with other criteria |
||
261 | * - Field list operators will be merged |
||
262 | * - Limit and offet will be overriden |
||
263 | * - Select fields list will be merged |
||
264 | * - Sort fields list will be merged |
||
265 | * @param array|CriteriaInterface $criteria |
||
266 | * @return CriteriaInterface |
||
267 | * @since v1.0 |
||
268 | */ |
||
269 | 80 | public function mergeWith($criteria) |
|
270 | { |
||
271 | 80 | if (is_array($criteria)) |
|
272 | { |
||
273 | $criteria = new static($criteria); |
||
274 | } |
||
275 | elseif (empty($criteria)) |
||
276 | { |
||
277 | 80 | return $this; |
|
278 | } |
||
279 | |||
280 | 40 | if ($this instanceof LimitableInterface && $criteria instanceof LimitableInterface && !empty($criteria->getLimit())) |
|
281 | { |
||
282 | $this->setLimit($criteria->getLimit()); |
||
283 | } |
||
284 | 40 | if ($this instanceof LimitableInterface && $criteria instanceof LimitableInterface && !empty($criteria->getOffset())) |
|
285 | { |
||
286 | $this->setOffset($criteria->getOffset()); |
||
287 | } |
||
288 | 40 | if ($this instanceof SortableInterface && $criteria instanceof SortableInterface && !empty($criteria->getSort())) |
|
289 | { |
||
290 | $this->setSort($criteria->getSort()); |
||
291 | } |
||
292 | 40 | if ($this instanceof SelectableInterface && $criteria instanceof SelectableInterface && !empty($criteria->getSelect())) |
|
293 | { |
||
294 | $this->select($criteria->getSelect()); |
||
295 | } |
||
296 | |||
297 | |||
298 | |||
299 | 40 | $this->_conditions = $this->_mergeConditions($this->_conditions, $criteria->getConditions()); |
|
300 | |||
301 | 40 | return $this; |
|
302 | } |
||
303 | |||
304 | 86 | private function _mergeConditions($source, $conditions) |
|
305 | { |
||
306 | 86 | $opTable = array_values(self::$operators); |
|
307 | 86 | foreach ($conditions as $fieldName => $conds) |
|
308 | { |
||
309 | if ( |
||
310 | 82 | is_array($conds) && |
|
311 | 82 | count(array_diff(array_keys($conds), $opTable)) == 0 |
|
312 | ) |
||
313 | { |
||
314 | 11 | if (isset($source[$fieldName]) && is_array($source[$fieldName])) |
|
315 | { |
||
316 | 1 | foreach ($source[$fieldName] as $operator => $value) |
|
317 | { |
||
318 | 1 | if (!in_array($operator, $opTable)) |
|
319 | { |
||
320 | 1 | unset($source[$fieldName][$operator]); |
|
321 | } |
||
322 | } |
||
323 | } |
||
324 | else |
||
325 | { |
||
326 | 11 | $source[$fieldName] = []; |
|
327 | } |
||
328 | |||
329 | 11 | foreach ($conds as $operator => $value) |
|
330 | { |
||
331 | 11 | $source[$fieldName][$operator] = $value; |
|
332 | } |
||
333 | } |
||
334 | else |
||
335 | { |
||
336 | 82 | $source[$fieldName] = $conds; |
|
337 | } |
||
338 | } |
||
339 | 86 | return $source; |
|
340 | } |
||
341 | |||
342 | /** |
||
343 | * If we have operator add it otherwise call parent implementation |
||
344 | * @since v1.0 |
||
345 | */ |
||
346 | 1 | public function __call($fieldName, $parameters) |
|
347 | { |
||
348 | 1 | if (isset($parameters[0])) |
|
349 | { |
||
350 | 1 | $operatorName = strtolower($parameters[0]); |
|
351 | } |
||
352 | 1 | if (array_key_exists(1, $parameters)) |
|
353 | { |
||
354 | 1 | $value = $parameters[1]; |
|
355 | } |
||
356 | 1 | if (is_numeric($operatorName)) |
|
357 | { |
||
358 | $operatorName = strtolower(trim($value)); |
||
359 | $value = (strtolower(trim($value)) === 'exists') ? true : false; |
||
360 | } |
||
361 | |||
362 | 1 | if (in_array($operatorName, array_keys(self::$operators))) |
|
363 | { |
||
364 | 1 | array_push($this->_workingFields, $fieldName); |
|
365 | 1 | $fieldName = implode('.', $this->_workingFields); |
|
366 | 1 | $this->_workingFields = []; |
|
367 | switch ($operatorName) |
||
368 | { |
||
369 | 1 | case 'exists': |
|
370 | $this->addCond($fieldName, $operatorName, true); |
||
371 | break; |
||
372 | 1 | case 'notexists': |
|
373 | $this->addCond($fieldName, $operatorName, false); |
||
374 | break; |
||
375 | default: |
||
376 | 1 | $this->addCond($fieldName, $operatorName, $value); |
|
377 | } |
||
378 | 1 | return $this; |
|
379 | } |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * @since v1.0.2 |
||
384 | */ |
||
385 | public function __get($name) |
||
386 | { |
||
387 | array_push($this->_workingFields, $name); |
||
388 | return $this; |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * @since v1.0.2 |
||
393 | */ |
||
394 | 11 | public function __set($name, $value) |
|
401 | |||
402 | /** |
||
403 | * Return query array |
||
404 | * @return array query array |
||
405 | * @since v1.0 |
||
406 | */ |
||
407 | 86 | public function getConditions() |
|
408 | { |
||
409 | 86 | $conditions = []; |
|
410 | 86 | foreach ($this->_rawConds as $c) |
|
411 | { |
||
412 | 82 | $conditions = $this->_makeCond($c[Conditions::FieldName], $c[Conditions::Operator], $c[Conditions::Value], $conditions); |
|
413 | } |
||
414 | 86 | return $this->_mergeConditions($this->_conditions, $conditions); |
|
415 | } |
||
416 | |||
417 | /** |
||
418 | * Set conditions |
||
419 | * @param array|Conditions $conditions |
||
420 | * @return Criteria |
||
421 | */ |
||
422 | public function setConditions($conditions) |
||
432 | |||
433 | /** |
||
434 | * Add condition |
||
435 | * If specified field already has a condition, values will be merged |
||
436 | * duplicates will be overriden by new values! |
||
437 | * |
||
438 | * NOTE: Should NOT be part of interface |
||
439 | * |
||
440 | * @param string $fieldName |
||
441 | * @param string $op operator |
||
442 | * @param mixed $value |
||
443 | * @since v1.0 |
||
444 | */ |
||
445 | 82 | public function addCond($fieldName, $op, $value) |
|
446 | { |
||
447 | 82 | $this->_rawConds[] = [ |
|
448 | 82 | Conditions::FieldName => $fieldName, |
|
449 | 82 | Conditions::Operator => $op, |
|
450 | 82 | Conditions::Value => $value |
|
451 | ]; |
||
452 | 82 | return $this; |
|
453 | } |
||
454 | |||
455 | /** |
||
456 | * @since v1.3.1 |
||
457 | * @deprecated since version number |
||
458 | */ |
||
459 | protected function getWorkingFields() |
||
463 | |||
464 | /** |
||
465 | * @since v1.3.1 |
||
466 | * @deprecated since version number |
||
467 | */ |
||
468 | protected function setWorkingFields(array $select) |
||
472 | |||
473 | /** |
||
474 | * Get condition |
||
475 | * If specified field already has a condition, values will be merged |
||
476 | * duplicates will be overriden by new values! |
||
477 | * @see getConditions |
||
478 | * @param string $fieldName |
||
479 | * @param string $op operator |
||
480 | * @param mixed $value |
||
481 | * @since v1.0 |
||
482 | */ |
||
483 | 82 | private function _makeCond($fieldName, $op, $value, $conditions = []) |
|
552 | |||
553 | } |
||
554 |
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.