Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Loader 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 Loader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
37 | abstract class Loader implements LoaderInterface |
||
38 | { |
||
39 | /** |
||
40 | * Default loading methods for ORM loaders. |
||
41 | */ |
||
42 | const INLOAD = 1; |
||
43 | const POSTLOAD = 2; |
||
44 | const JOIN = 3; |
||
45 | |||
46 | /** |
||
47 | * Relation type is required to correctly resolve foreign record class based on relation |
||
48 | * definition. |
||
49 | */ |
||
50 | const RELATION_TYPE = null; |
||
51 | |||
52 | /** |
||
53 | * Default load method (inload or postload). |
||
54 | */ |
||
55 | const LOAD_METHOD = null; |
||
56 | |||
57 | /** |
||
58 | * Internal loader constant used to decide how to aggregate data tree, true for relations like |
||
59 | * MANY TO MANY or HAS MANY. |
||
60 | */ |
||
61 | const MULTIPLE = false; |
||
62 | |||
63 | /** |
||
64 | * Count of Loaders requested data alias. |
||
65 | * |
||
66 | * @var int |
||
67 | */ |
||
68 | private static $counter = 0; |
||
69 | |||
70 | /** |
||
71 | * Unique loader data alias (only for loaders, not joiners). |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | private $alias = ''; |
||
76 | |||
77 | /** |
||
78 | * Helper structure used to prevent data duplication when LEFT JOIN multiplies parent records. |
||
79 | * |
||
80 | * @invisible |
||
81 | * @var array |
||
82 | */ |
||
83 | private $duplicates = []; |
||
84 | |||
85 | /** |
||
86 | * Loader configuration options, can be edited using setOptions method or while declaring loader |
||
87 | * in Selector. |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | protected $options = [ |
||
92 | 'method' => null, |
||
93 | 'alias' => null, |
||
94 | 'using' => null, |
||
95 | 'where' => null |
||
96 | ]; |
||
97 | |||
98 | /** |
||
99 | * Result of data compilation, only populated in cases where loader is primary Selector loader. |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | protected $result = []; |
||
104 | |||
105 | /** |
||
106 | * Container related to parent loader. Loaded data must be loaded using this container. |
||
107 | * |
||
108 | * @var string |
||
109 | */ |
||
110 | protected $container = ''; |
||
111 | |||
112 | /** |
||
113 | * Indication that loaded already set columns and conditions to parent Selector. |
||
114 | * |
||
115 | * @var bool |
||
116 | */ |
||
117 | protected $configured = false; |
||
118 | |||
119 | /** |
||
120 | * Set of columns to be fetched from resulted query. |
||
121 | * |
||
122 | * @var array |
||
123 | */ |
||
124 | protected $dataColumns = []; |
||
125 | |||
126 | /** |
||
127 | * Loader data offset in resulted query row provided by parent Selector or Loader. |
||
128 | * |
||
129 | * @var int |
||
130 | */ |
||
131 | protected $dataOffset = 0; |
||
132 | |||
133 | /** |
||
134 | * Relation definition options if any. |
||
135 | * |
||
136 | * @var array |
||
137 | */ |
||
138 | protected $definition = []; |
||
139 | |||
140 | /** |
||
141 | * Inner (nested) loaders. |
||
142 | * |
||
143 | * @var LoaderInterface[] |
||
144 | */ |
||
145 | protected $loaders = []; |
||
146 | |||
147 | /** |
||
148 | * Loaders used purely for conditional purposes. Only ORM loaders can do that. |
||
149 | * |
||
150 | * @var Loader[] |
||
151 | */ |
||
152 | protected $joiners = []; |
||
153 | |||
154 | /** |
||
155 | * Set of keys requested by inner loaders to be pre-aggregated while query parsing. This |
||
156 | * structure if populated when new sub loaded registered. |
||
157 | * |
||
158 | * @var array |
||
159 | */ |
||
160 | protected $referenceKeys = []; |
||
161 | |||
162 | /** |
||
163 | * Chunks of parsed data associated with their reference key name and it's value. Used to |
||
164 | * compile data tree via php references. |
||
165 | * |
||
166 | * @var array |
||
167 | */ |
||
168 | protected $references = []; |
||
169 | |||
170 | /** |
||
171 | * Related record schema. |
||
172 | * |
||
173 | * @invisible |
||
174 | * @var array |
||
175 | */ |
||
176 | protected $schema = []; |
||
177 | |||
178 | /** |
||
179 | * ORM Loaders can only be nested into ORM Loaders. |
||
180 | * |
||
181 | * @invisible |
||
182 | * @var Loader|null |
||
183 | */ |
||
184 | protected $parent = null; |
||
185 | |||
186 | /** |
||
187 | * @invisible |
||
188 | * @var ORM |
||
189 | */ |
||
190 | protected $orm = null; |
||
191 | |||
192 | /** |
||
193 | * {@inheritdoc} |
||
194 | */ |
||
195 | public function __construct( |
||
196 | ORM $orm, |
||
197 | $container, |
||
198 | array $definition = [], |
||
199 | LoaderInterface $parent = null |
||
200 | ) { |
||
201 | $this->orm = $orm; |
||
202 | |||
203 | //Related record schema |
||
204 | $this->schema = $orm->schema($definition[static::RELATION_TYPE]); |
||
205 | |||
206 | $this->container = $container; |
||
207 | $this->definition = $definition; |
||
208 | $this->parent = $parent; |
||
|
|||
209 | |||
210 | //Compiling options |
||
211 | $this->options['method'] = static::LOAD_METHOD; |
||
212 | |||
213 | if (!empty($parent)) { |
||
214 | if (!$parent instanceof Loader || $parent->getDatabase() != $this->getDatabase()) { |
||
215 | //We have to force post-load (separate query) if parent loader database is different |
||
216 | $this->options['method'] = self::POSTLOAD; |
||
217 | } |
||
218 | } |
||
219 | |||
220 | $this->dataColumns = array_keys($this->schema[ORM::M_COLUMNS]); |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Update loader options. |
||
225 | * |
||
226 | * @param array $options |
||
227 | * @return $this |
||
228 | * @throws LoaderException |
||
229 | */ |
||
230 | public function setOptions(array $options = []) |
||
244 | |||
245 | /** |
||
246 | * Table name loader relates to. |
||
247 | * |
||
248 | * @return mixed |
||
249 | */ |
||
250 | public function getTable() |
||
254 | |||
255 | /** |
||
256 | * Every loader declares an unique alias for it's source table based on options or based on |
||
257 | * position in loaders chain. In addition, every loader responsible for data loading will add |
||
258 | * "_data" postfix to it's alias. |
||
259 | * |
||
260 | * @return string |
||
261 | */ |
||
262 | public function getAlias() |
||
263 | { |
||
264 | if (!empty($this->options['using'])) { |
||
265 | //We are using another relation (presumably defined by with() to load data). |
||
266 | return $this->options['using']; |
||
267 | } |
||
268 | |||
269 | if (!empty($this->options['alias'])) { |
||
270 | return $this->options['alias']; |
||
271 | } |
||
272 | |||
273 | //We are not really worrying about default loader aliases, joiners more important |
||
274 | if ($this->isLoadable()) { |
||
275 | if (!empty($this->alias)) { |
||
276 | //Alias was already created |
||
277 | return $this->alias; |
||
278 | } |
||
279 | |||
280 | //New alias is pretty simple and short |
||
281 | return $this->alias = 'd' . decoct(++self::$counter); |
||
282 | } |
||
283 | |||
284 | if (empty($this->parent)) { |
||
285 | $alias = $this->getTable(); |
||
286 | } elseif ($this->parent instanceof RootLoader) { |
||
287 | //This is first level of relation loading, we can use relation name by itself |
||
288 | $alias = $this->container; |
||
289 | } else { |
||
290 | //Let's use parent alias to continue chain |
||
291 | $alias = $this->parent->getAlias() . '_' . $this->container; |
||
292 | } |
||
293 | |||
294 | return $alias; |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * Database name loader relates to. |
||
299 | * |
||
300 | * @return mixed |
||
301 | */ |
||
302 | public function getDatabase() |
||
306 | |||
307 | /** |
||
308 | * Instance of Dbal\Database data associated with loader instance, used as primary database |
||
309 | * for selector is loader defined as primary selection loader. |
||
310 | * |
||
311 | * @return Database |
||
312 | */ |
||
313 | public function dbalDatabase() |
||
317 | |||
318 | /** |
||
319 | * Get primary key name related to associated record. |
||
320 | * |
||
321 | * @return string|null |
||
322 | */ |
||
323 | public function getPrimaryKey() |
||
324 | { |
||
325 | if (!isset($this->schema[ORM::M_PRIMARY_KEY])) { |
||
326 | return null; |
||
327 | } |
||
328 | |||
329 | return $this->getAlias() . '.' . $this->schema[ORM::M_PRIMARY_KEY]; |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Pre-load data on inner relation or relation chain. Method automatically called by Selector, |
||
334 | * see load() method. |
||
335 | * |
||
336 | * @see Selector::load() |
||
337 | * @param string $relation Relation name, or chain of relations separated by. |
||
338 | * @param array $options Loader options (will be applied to last chain element only). |
||
339 | * @return LoaderInterface |
||
340 | * @throws LoaderException |
||
341 | */ |
||
342 | public function loader($relation, array $options = []) |
||
343 | { |
||
344 | View Code Duplication | if (($position = strpos($relation, '.')) !== false) { |
|
345 | //Chain of relations provided |
||
346 | $nested = $this->loader(substr($relation, 0, $position), []); |
||
347 | |||
348 | if (empty($nested) || !$nested instanceof self) { |
||
349 | //todo: Think about the options |
||
350 | throw new LoaderException( |
||
351 | "Only ORM loaders can be used to generate/configure chain of relation loaders." |
||
352 | ); |
||
353 | } |
||
354 | |||
355 | //Recursively (will work only with ORM loaders). |
||
356 | return $nested->loader(substr($relation, $position + 1), $options); |
||
357 | } |
||
358 | |||
359 | View Code Duplication | if (!isset($this->schema[ORM::M_RELATIONS][$relation])) { |
|
360 | $container = $this->container ?: $this->schema[ORM::M_ROLE_NAME]; |
||
361 | |||
362 | throw new LoaderException( |
||
363 | "Undefined relation '{$relation}' under '{$container}'." |
||
364 | ); |
||
365 | } |
||
366 | |||
367 | if (isset($this->loaders[$relation])) { |
||
368 | $nested = $this->loaders[$relation]; |
||
369 | if (!$nested instanceof self) { |
||
370 | throw new LoaderException( |
||
371 | "Only ORM loaders can be used to generate/configure chain of relation loaders." |
||
372 | ); |
||
373 | } |
||
374 | |||
375 | //Updating existed loaded options |
||
376 | $nested->setOptions($options); |
||
377 | |||
378 | return $nested; |
||
379 | } |
||
380 | |||
381 | $relationOptions = $this->schema[ORM::M_RELATIONS][$relation]; |
||
382 | |||
383 | //Asking ORM for loader instance |
||
384 | $loader = $this->orm->loader( |
||
385 | $relationOptions[ORM::R_TYPE], |
||
386 | $relation, |
||
387 | $relationOptions[ORM::R_DEFINITION], |
||
388 | $this |
||
389 | ); |
||
390 | |||
391 | if (!empty($options) && !$loader instanceof self) { |
||
392 | //todo: think about alternatives again |
||
393 | throw new LoaderException( |
||
394 | "Only ORM loaders can be used to generate/configure chain of relation loaders." |
||
395 | ); |
||
396 | } |
||
397 | |||
398 | $loader->setOptions($options); |
||
399 | $this->loaders[$relation] = $loader; |
||
400 | |||
401 | if ($referenceKey = $loader->getReferenceKey()) { |
||
402 | /** |
||
403 | * Inner loader requests parent to pre-collect some keys so it can build tree using |
||
404 | * references without looking up for correct record every time. |
||
405 | */ |
||
406 | $this->referenceKeys[] = $referenceKey; |
||
407 | $this->referenceKeys = array_unique($this->referenceKeys); |
||
408 | } |
||
409 | |||
410 | return $loader; |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Filter data on inner relation or relation chain. Method automatically called by Selector, |
||
415 | * see with() method. Logic is identical to loader() method. |
||
416 | * |
||
417 | * @see Selector::load() |
||
418 | * @param string $relation Relation name, or chain of relations separated by. |
||
419 | * @param array $options Loader options (will be applied to last chain element only). |
||
420 | * @return Loader |
||
421 | * @throws LoaderException |
||
422 | */ |
||
423 | public function joiner($relation, array $options = []) |
||
424 | { |
||
425 | //We have to force joining method for full chain |
||
426 | $options['method'] = self::JOIN; |
||
427 | |||
428 | View Code Duplication | if (($position = strpos($relation, '.')) !== false) { |
|
429 | //Chain of relations provided |
||
430 | $nested = $this->joiner(substr($relation, 0, $position), []); |
||
431 | if (empty($nested) || !$nested instanceof self) { |
||
432 | //todo: DRY |
||
433 | throw new LoaderException( |
||
434 | "Only ORM loaders can be used to generate/configure chain of relation joiners." |
||
435 | ); |
||
436 | } |
||
437 | |||
438 | //Recursively (will work only with ORM loaders). |
||
439 | return $nested->joiner(substr($relation, $position + 1), $options); |
||
440 | } |
||
441 | |||
442 | View Code Duplication | if (!isset($this->schema[ORM::M_RELATIONS][$relation])) { |
|
443 | $container = $this->container ?: $this->schema[ORM::M_ROLE_NAME]; |
||
444 | |||
445 | throw new LoaderException( |
||
446 | "Undefined relation '{$relation}' under '{$container}'." |
||
447 | ); |
||
448 | } |
||
449 | |||
450 | if (isset($this->joiners[$relation])) { |
||
451 | //Updating existed joiner options |
||
452 | return $this->joiners[$relation]->setOptions($options); |
||
453 | } |
||
454 | |||
455 | $relationOptions = $this->schema[ORM::M_RELATIONS][$relation]; |
||
456 | |||
457 | $joiner = $this->orm->loader( |
||
458 | $relationOptions[ORM::R_TYPE], |
||
459 | $relation, |
||
460 | $relationOptions[ORM::R_DEFINITION], |
||
461 | $this |
||
462 | ); |
||
463 | |||
464 | if (!$joiner instanceof self) { |
||
465 | //todo: DRY |
||
466 | throw new LoaderException( |
||
467 | "Only ORM loaders can be used to generate/configure chain of relation joiners." |
||
468 | ); |
||
469 | } |
||
470 | |||
471 | return $this->joiners[$relation] = $joiner->setOptions($options); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * {@inheritdoc} |
||
476 | */ |
||
477 | public function isMultiple() |
||
478 | { |
||
479 | return static::MULTIPLE; |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * {@inheritdoc} |
||
484 | */ |
||
485 | public function getReferenceKey() |
||
486 | { |
||
487 | //In most of cases reference key is inner key name (parent "ID" field name), don't be confused |
||
488 | //by INNER_KEY, remember that we building relation from parent record point of view |
||
489 | return $this->definition[RecordEntity::INNER_KEY]; |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * {@inheritdoc} |
||
494 | */ |
||
495 | public function aggregatedKeys($referenceKey) |
||
496 | { |
||
497 | if (!isset($this->references[$referenceKey])) { |
||
498 | return []; |
||
499 | } |
||
500 | |||
501 | return array_unique(array_keys($this->references[$referenceKey])); |
||
502 | } |
||
503 | |||
504 | /** |
||
505 | * Create selector dedicated to load data for current loader. |
||
506 | * |
||
507 | * @return RecordSelector|null |
||
508 | */ |
||
509 | public function createSelector() |
||
510 | { |
||
511 | if (!$this->isLoadable()) { |
||
512 | return null; |
||
513 | } |
||
514 | |||
515 | $selector = $this->orm->selector($this->definition[static::RELATION_TYPE], $this); |
||
516 | |||
517 | //Setting columns to be loaded |
||
518 | $this->configureColumns($selector); |
||
519 | |||
520 | foreach ($this->loaders as $loader) { |
||
521 | if ($loader instanceof self) { |
||
522 | //Allowing sub loaders to configure required columns and conditions as well |
||
523 | $loader->configureSelector($selector); |
||
524 | } |
||
525 | } |
||
526 | |||
527 | foreach ($this->joiners as $joiner) { |
||
528 | //Joiners must configure selector as well |
||
529 | $joiner->configureSelector($selector); |
||
530 | } |
||
531 | |||
532 | return $selector; |
||
533 | } |
||
534 | |||
535 | /** |
||
536 | * Configure provided selector with required joins, columns and conditions, in addition method |
||
537 | * must pass configuration to sub loaders. |
||
538 | * |
||
539 | * Method called by Selector when loader set as primary selection loader. |
||
540 | * |
||
541 | * @param RecordSelector $selector |
||
542 | */ |
||
543 | public function configureSelector(RecordSelector $selector) |
||
544 | { |
||
545 | if (!$this->isJoinable()) { |
||
546 | //Loader can be used not only for loading but purely for filering |
||
547 | if (empty($this->parent)) { |
||
548 | foreach ($this->loaders as $loader) { |
||
549 | if ($loader instanceof self) { |
||
550 | $loader->configureSelector($selector); |
||
551 | } |
||
552 | } |
||
553 | |||
554 | foreach ($this->joiners as $joiner) { |
||
555 | //Nested joiners |
||
556 | $joiner->configureSelector($selector); |
||
557 | } |
||
558 | } |
||
559 | |||
560 | return; |
||
561 | } |
||
562 | |||
563 | if (!$this->configured) { |
||
564 | //We never configured loader columns before |
||
565 | $this->configureColumns($selector); |
||
566 | |||
567 | //Inload conditions and etc |
||
568 | if (empty($this->options['using']) && !empty($this->parent)) { |
||
569 | $this->clarifySelector($selector); |
||
570 | } |
||
571 | |||
572 | $this->configured = true; |
||
573 | } |
||
574 | |||
575 | foreach ($this->loaders as $loader) { |
||
576 | if ($loader instanceof self) { |
||
577 | $loader->configureSelector($selector); |
||
578 | } |
||
579 | } |
||
580 | |||
581 | foreach ($this->joiners as $joiner) { |
||
582 | $joiner->configureSelector($selector); |
||
583 | } |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * Implementation specific selector configuration, must create required joins, conditions and |
||
588 | * etc. |
||
589 | * |
||
590 | * @param RecordSelector $selector |
||
591 | */ |
||
592 | abstract protected function clarifySelector(RecordSelector $selector); |
||
593 | |||
594 | /** |
||
595 | * Parse QueryResult provided by parent loaders and populate data tree. Loader must pass parsing |
||
596 | * to inner loaders also. |
||
597 | * |
||
598 | * @param QueryResult $result |
||
599 | * @param int $rowsCount |
||
600 | * @return array |
||
601 | */ |
||
602 | public function parseResult(QueryResult $result, &$rowsCount) |
||
603 | { |
||
604 | foreach ($result as $row) { |
||
605 | $this->parseRow($row); |
||
606 | $rowsCount++; |
||
607 | } |
||
608 | |||
609 | return $this->result; |
||
610 | } |
||
611 | |||
612 | /** |
||
613 | * {@inheritdoc} |
||
614 | * |
||
615 | * Method will clarify Loader data tree result using nested loaders. |
||
616 | */ |
||
617 | public function loadData() |
||
618 | { |
||
619 | foreach ($this->loaders as $loader) { |
||
620 | if ($loader instanceof self && !$loader->isJoinable()) { |
||
621 | if (!empty($selector = $loader->createSelector())) { |
||
622 | //Data will be automatically linked via references and mount method |
||
623 | $selector->fetchData(); |
||
624 | } |
||
625 | } else { |
||
626 | //Some other loader type or loader requested separate query to be created |
||
627 | $loader->loadData(); |
||
628 | } |
||
629 | } |
||
630 | } |
||
631 | |||
632 | /** |
||
633 | * Get compiled data tree, method must be called only if loader were feeded with QueryResult |
||
634 | * using parseResult() method. Attention, method must be called AFTER loadData() with additional |
||
635 | * loaders were executed. |
||
636 | * |
||
637 | * @see loadData() |
||
638 | * @see parseResult() |
||
639 | * @return array |
||
640 | */ |
||
641 | public function getResult() |
||
645 | |||
646 | /** |
||
647 | * {@inheritdoc} |
||
648 | * |
||
649 | * Data will be mounted using references. |
||
650 | */ |
||
651 | public function mount($container, $key, $criteria, array &$data, $multiple = false) |
||
652 | { |
||
653 | foreach ($this->references[$key][$criteria] as &$subset) { |
||
654 | if ($multiple) { |
||
655 | if (isset($subset[$container]) && in_array($data, $subset[$container])) { |
||
675 | |||
676 | /** |
||
677 | * {@inheritdoc} |
||
678 | * |
||
679 | * @param bool $reconfigure Use this option to reset configured flag to force query |
||
680 | * clarification on next query creation. |
||
681 | */ |
||
682 | public function clean($reconfigure = false) |
||
702 | |||
703 | /** |
||
704 | * Cloning selector presets |
||
705 | */ |
||
706 | public function __clone() |
||
716 | |||
717 | /** |
||
718 | * Destruct loader. |
||
719 | */ |
||
720 | public function __destruct() |
||
726 | |||
727 | /** |
||
728 | * Indicates that loader columns must be included into query statement. |
||
729 | * |
||
730 | * @return bool |
||
731 | */ |
||
732 | protected function isLoadable() |
||
741 | |||
742 | /** |
||
743 | * Indicated that loaded must generate JOIN statement. |
||
744 | * |
||
745 | * @return bool |
||
746 | */ |
||
747 | protected function isJoinable() |
||
755 | |||
756 | /** |
||
757 | * If loader is joinable we can calculate join type based on way loader going to be used |
||
758 | * (loading or filtering). |
||
759 | * |
||
760 | * @return string |
||
761 | * @throws LoaderException |
||
762 | */ |
||
763 | protected function joinType() |
||
771 | |||
772 | /** |
||
773 | * Fetch record columns from query row, must use data offset to slice required part of query. |
||
774 | * |
||
775 | * @param array $row |
||
776 | * @return array |
||
777 | */ |
||
778 | protected function fetchData(array $row) |
||
786 | |||
787 | /** |
||
788 | * In many cases (for example if you have inload of HAS_MANY relation) record data can be |
||
789 | * replicated by many result rows (duplicated). To prevent wrong data linking we have to |
||
790 | * deduplicate such records. This is only internal loader functionality and required due data |
||
791 | * tree are built using php references. |
||
792 | * |
||
793 | * Method will return true if data is unique handled before and false in opposite case. |
||
794 | * Provided data array will be automatically linked with it's unique state using references. |
||
795 | * |
||
796 | * @param array $data Reference to parsed record data, reference will be pointed to valid and |
||
797 | * existed data segment if such data was already parsed. |
||
798 | * @return bool |
||
799 | */ |
||
800 | protected function deduplicate(array &$data) |
||
828 | |||
829 | /** |
||
830 | * Generate sql identifier using loader alias and value from relation definition. |
||
831 | * |
||
832 | * Example: |
||
833 | * $this->getKey(Record::OUTER_KEY); |
||
834 | * |
||
835 | * @param string $key |
||
836 | * @return string|null |
||
837 | */ |
||
838 | protected function getKey($key) |
||
846 | |||
847 | /** |
||
848 | * SQL identified to parent record outer key (usually primary key). |
||
849 | * |
||
850 | * @return string |
||
851 | * @throws LoaderException |
||
852 | */ |
||
853 | protected function getParentKey() |
||
861 | |||
862 | /** |
||
863 | * Configure columns required for loader data selection. |
||
864 | * |
||
865 | * @param RecordSelector $selector |
||
866 | */ |
||
867 | protected function configureColumns(RecordSelector $selector) |
||
875 | |||
876 | /** |
||
877 | * Reference criteria is value to be used to mount data into parent loader tree. |
||
878 | * |
||
879 | * Example: |
||
880 | * User has many Posts (relation "posts"), user primary is ID, post inner key pointing to user |
||
881 | * is USER_ID. Post loader must request User data loader to create references based on ID field |
||
882 | * values. Once Post data were parsed we can mount it under parent user using mount method: |
||
883 | * |
||
884 | * $this->parent->mount("posts", "ID", $data["USER_ID"], $data, true); //true = multiple |
||
885 | * |
||
886 | * @see getReferenceKey() |
||
887 | * @param array $data |
||
888 | * @return mixed |
||
889 | */ |
||
890 | View Code Duplication | protected function fetchCriteria(array $data) |
|
898 | |||
899 | /** |
||
900 | * Parse single result row to generate data tree. Must pass parsing to every nested loader. |
||
901 | * |
||
902 | * @param array $row |
||
903 | * @return bool |
||
904 | */ |
||
905 | private function parseRow(array $row) |
||
950 | |||
951 | /** |
||
952 | * Parse data using nested loaders. |
||
953 | * |
||
954 | * @param array $row |
||
955 | */ |
||
956 | private function parseNested(array $row) |
||
964 | |||
965 | /** |
||
966 | * Create internal references cache based on requested keys. For example, if we have request for |
||
967 | * "id" as reference key, every record will create following structure: |
||
968 | * $this->references[id][ID_VALUE] = ITEM |
||
969 | * |
||
970 | * Only deduplicated data must be collected! |
||
971 | * |
||
972 | * @see deduplicate() |
||
973 | * @param array $data |
||
974 | */ |
||
975 | private function collectReferences(array &$data) |
||
982 | } |
||
983 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.