Total Complexity | 86 |
Total Lines | 859 |
Duplicated Lines | 0 % |
Changes | 9 | ||
Bugs | 0 | Features | 0 |
Complex classes like CollectionLoader 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.
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 CollectionLoader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class CollectionLoader implements |
||
33 | FilterCollectionInterface, |
||
34 | OrderCollectionInterface, |
||
35 | LoggerAwareInterface |
||
36 | { |
||
37 | use LoggerAwareTrait; |
||
38 | |||
39 | /** |
||
40 | * The source to load objects from. |
||
41 | * |
||
42 | * @var SourceInterface |
||
43 | */ |
||
44 | private $source; |
||
45 | |||
46 | /** |
||
47 | * The model to load the collection from. |
||
48 | * |
||
49 | * @var ModelInterface |
||
50 | */ |
||
51 | private $model; |
||
52 | |||
53 | /** |
||
54 | * Store the factory instance for the current class. |
||
55 | * |
||
56 | * @var FactoryInterface |
||
57 | */ |
||
58 | private $factory; |
||
59 | |||
60 | /** |
||
61 | * The callback routine applied to every object added to the collection. |
||
62 | * |
||
63 | * @var callable|null |
||
64 | */ |
||
65 | private $callback; |
||
66 | |||
67 | /** |
||
68 | * The field which defines the data's model. |
||
69 | * |
||
70 | * @var string|null |
||
71 | */ |
||
72 | private $dynamicTypeField; |
||
73 | |||
74 | /** |
||
75 | * The class name of the collection to use. |
||
76 | * |
||
77 | * Must be a fully-qualified PHP namespace and an implementation of {@see ArrayAccess}. |
||
78 | * |
||
79 | * @var string |
||
80 | */ |
||
81 | private $collectionClass = Collection::class; |
||
82 | |||
83 | /** |
||
84 | * Return a new CollectionLoader object. |
||
85 | * |
||
86 | * @param array $data The loader's dependencies. |
||
87 | */ |
||
88 | public function __construct(array $data) |
||
89 | { |
||
90 | if (!isset($data['logger'])) { |
||
91 | $data['logger'] = new NullLogger(); |
||
92 | } |
||
93 | |||
94 | $this->setLogger($data['logger']); |
||
95 | |||
96 | if (isset($data['collection'])) { |
||
97 | $this->setCollectionClass($data['collection']); |
||
98 | } |
||
99 | |||
100 | if (isset($data['factory'])) { |
||
101 | $this->setFactory($data['factory']); |
||
102 | } |
||
103 | |||
104 | if (isset($data['model'])) { |
||
105 | $this->setModel($data['model']); |
||
106 | } |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Set an object model factory. |
||
111 | * |
||
112 | * @param FactoryInterface $factory The model factory, to create objects. |
||
113 | * @return self |
||
114 | */ |
||
115 | public function setFactory(FactoryInterface $factory) |
||
116 | { |
||
117 | $this->factory = $factory; |
||
118 | |||
119 | return $this; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Retrieve the object model factory. |
||
124 | * |
||
125 | * @throws RuntimeException If the model factory was not previously set. |
||
126 | * @return FactoryInterface |
||
127 | */ |
||
128 | protected function factory() |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Create a new model. |
||
141 | * |
||
142 | * @return ModelInterface |
||
143 | */ |
||
144 | public function createModel() |
||
145 | { |
||
146 | $obj = $this->factory()->create($this->modelClass()); |
||
147 | return $obj; |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Create a new model from a dataset. |
||
152 | * |
||
153 | * @param array $data The model data. |
||
154 | * @return ModelInterface |
||
155 | */ |
||
156 | protected function createModelFromData(array $data) |
||
157 | { |
||
158 | $obj = $this->factory()->create($this->dynamicModelClass($data)); |
||
159 | return $obj; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Set the loader settings. |
||
164 | * |
||
165 | * @param array $data Data to assign to the loader. |
||
166 | * @return self |
||
167 | */ |
||
168 | public function setData(array $data) |
||
169 | { |
||
170 | foreach ($data as $key => $val) { |
||
171 | $setter = $this->setter($key); |
||
172 | if (is_callable([ $this, $setter ])) { |
||
173 | $this->{$setter}($val); |
||
174 | } else { |
||
175 | $this->{$key} = $val; |
||
176 | } |
||
177 | } |
||
178 | |||
179 | return $this; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Retrieve the source to load objects from. |
||
184 | * |
||
185 | * @throws RuntimeException If no source has been defined. |
||
186 | * @return SourceInterface |
||
187 | */ |
||
188 | public function source() |
||
189 | { |
||
190 | if ($this->source === null) { |
||
191 | throw new RuntimeException('No source set.'); |
||
192 | } |
||
193 | |||
194 | return $this->source; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Set the source to load objects from. |
||
199 | * |
||
200 | * @param SourceInterface $source A data source. |
||
201 | * @return self |
||
202 | */ |
||
203 | public function setSource(SourceInterface $source) |
||
204 | { |
||
205 | $source->reset(); |
||
|
|||
206 | |||
207 | $this->source = $source; |
||
208 | |||
209 | return $this; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * Reset everything but the model. |
||
214 | * |
||
215 | * @return self |
||
216 | */ |
||
217 | public function reset() |
||
218 | { |
||
219 | if ($this->source) { |
||
220 | $this->source()->reset(); |
||
221 | } |
||
222 | |||
223 | $this->callback = null; |
||
224 | $this->dynamicTypeField = null; |
||
225 | |||
226 | return $this; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Retrieve the object model. |
||
231 | * |
||
232 | * @throws RuntimeException If no model has been defined. |
||
233 | * @return ModelInterface |
||
234 | */ |
||
235 | public function model() |
||
236 | { |
||
237 | if ($this->model === null) { |
||
238 | throw new RuntimeException('The collection loader must have a model.'); |
||
239 | } |
||
240 | |||
241 | return $this->model; |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Determine if the loader has an object model. |
||
246 | * |
||
247 | * @return boolean |
||
248 | */ |
||
249 | public function hasModel() |
||
250 | { |
||
251 | return !!$this->model; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Set the model to use for the loaded objects. |
||
256 | * |
||
257 | * @param string|ModelInterface $model An object model. |
||
258 | * @throws InvalidArgumentException If the given argument is not a model. |
||
259 | * @return self |
||
260 | */ |
||
261 | public function setModel($model) |
||
262 | { |
||
263 | if (is_string($model)) { |
||
264 | $model = $this->factory()->get($model); |
||
265 | } |
||
266 | |||
267 | if (!$model instanceof ModelInterface) { |
||
268 | throw new InvalidArgumentException( |
||
269 | sprintf( |
||
270 | 'The model must be an instance of "%s"', |
||
271 | ModelInterface::class |
||
272 | ) |
||
273 | ); |
||
274 | } |
||
275 | |||
276 | $this->model = $model; |
||
277 | |||
278 | $this->setSource($model->source()); |
||
279 | |||
280 | return $this; |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Retrieve the model class. |
||
285 | * |
||
286 | * @return string |
||
287 | */ |
||
288 | public function modelClass() |
||
289 | { |
||
290 | return get_class($this->model()); |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Retrieve the model class. |
||
295 | * |
||
296 | * @param array $data The model data. |
||
297 | * @return string |
||
298 | */ |
||
299 | protected function dynamicModelClass(array $data) |
||
300 | { |
||
301 | $field = $this->dynamicTypeField(); |
||
302 | if ($field && isset($data[$field])) { |
||
303 | return $data[$field]; |
||
304 | } |
||
305 | |||
306 | return $this->modelClass(); |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * @return string|null |
||
311 | */ |
||
312 | public function dynamicTypeField() |
||
313 | { |
||
314 | return $this->dynamicTypeField; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * Determine if the model has a dynamic object type. |
||
319 | * |
||
320 | * @return boolean |
||
321 | */ |
||
322 | public function hasDynamicTypeField() |
||
323 | { |
||
324 | return !!$this->dynamicTypeField; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * @param string $field The field to use for dynamic object type. |
||
329 | * @throws InvalidArgumentException If the field is not a string. |
||
330 | * @return self |
||
331 | */ |
||
332 | public function setDynamicTypeField($field) |
||
333 | { |
||
334 | if (!is_string($field)) { |
||
335 | throw new InvalidArgumentException( |
||
336 | 'Dynamic type field must be a string' |
||
337 | ); |
||
338 | } |
||
339 | |||
340 | $this->dynamicTypeField = $field; |
||
341 | |||
342 | return $this; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Alias of {@see SourceInterface::properties()} |
||
347 | * |
||
348 | * @return array |
||
349 | */ |
||
350 | public function properties() |
||
351 | { |
||
352 | return $this->source()->properties(); |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Alias of {@see SourceInterface::setProperties()} |
||
357 | * |
||
358 | * @param array $properties An array of property identifiers. |
||
359 | * @return self |
||
360 | */ |
||
361 | public function setProperties(array $properties) |
||
362 | { |
||
363 | $this->source()->setProperties($properties); |
||
364 | |||
365 | return $this; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Alias of {@see SourceInterface::addProperty()} |
||
370 | * |
||
371 | * @param string $property A property identifier. |
||
372 | * @return self |
||
373 | */ |
||
374 | public function addProperty($property) |
||
375 | { |
||
376 | $this->source()->addProperty($property); |
||
377 | |||
378 | return $this; |
||
379 | } |
||
380 | |||
381 | /** |
||
382 | * Set "search" keywords to filter multiple properties. |
||
383 | * |
||
384 | * @param array $keywords An array of keywords and properties. |
||
385 | * Expected format: `[ "search query", [ "field names…" ] ]`. |
||
386 | * @return self |
||
387 | */ |
||
388 | public function setKeywords(array $keywords) |
||
389 | { |
||
390 | foreach ($keywords as $query) { |
||
391 | $keyword = $query[0]; |
||
392 | $properties = (isset($query[1]) ? (array)$query[1] : null); |
||
393 | $this->addKeyword($keyword, $properties); |
||
394 | } |
||
395 | |||
396 | return $this; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Add a "search" keyword filter to multiple properties. |
||
401 | * |
||
402 | * @param string $keyword A value to match among $properties. |
||
403 | * @param array $properties One or more of properties to search amongst. |
||
404 | * @return self |
||
405 | */ |
||
406 | public function addKeyword($keyword, array $properties = null) |
||
407 | { |
||
408 | if ($properties === null) { |
||
409 | $properties = []; |
||
410 | } |
||
411 | |||
412 | foreach ($properties as $propertyIdent) { |
||
413 | $val = ('%'.$keyword.'%'); |
||
414 | $this->addFilter([ |
||
415 | 'property' => $propertyIdent, |
||
416 | 'operator' => 'LIKE', |
||
417 | 'value' => $val, |
||
418 | 'operand' => 'OR' |
||
419 | ]); |
||
420 | } |
||
421 | |||
422 | return $this; |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Alias of {@see SourceInterface::filters()} |
||
427 | * |
||
428 | * @return FilterInterface[] |
||
429 | */ |
||
430 | public function filters() |
||
431 | { |
||
432 | return $this->source()->filters(); |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Alias of {@see SourceInterface::hasFilters()} |
||
437 | * |
||
438 | * @return boolean |
||
439 | */ |
||
440 | public function hasFilters() |
||
441 | { |
||
442 | return $this->source()->hasFilters(); |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Alias of {@see SourceInterface::setFilters()} |
||
447 | * |
||
448 | * @param array $filters An array of filters. |
||
449 | * @return self |
||
450 | */ |
||
451 | public function setFilters(array $filters) |
||
452 | { |
||
453 | $this->source()->setFilters($filters); |
||
454 | return $this; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Alias of {@see SourceInterface::addFilters()} |
||
459 | * |
||
460 | * @param array $filters An array of filters. |
||
461 | * @return self |
||
462 | */ |
||
463 | public function addFilters(array $filters) |
||
464 | { |
||
465 | foreach ($filters as $f) { |
||
466 | $this->addFilter($f); |
||
467 | } |
||
468 | return $this; |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * Alias of {@see SourceInterface::addFilter()} |
||
473 | * |
||
474 | * @param mixed $param The property to filter by, |
||
475 | * a {@see FilterInterface} object, |
||
476 | * or a filter array structure. |
||
477 | * @param mixed $value Optional value for the property to compare against. |
||
478 | * Only used if the first argument is a string. |
||
479 | * @param array $options Optional extra settings to apply on the filter. |
||
480 | * @return self |
||
481 | */ |
||
482 | public function addFilter($param, $value = null, array $options = null) |
||
483 | { |
||
484 | $this->source()->addFilter($param, $value, $options); |
||
485 | return $this; |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Alias of {@see SourceInterface::orders()} |
||
490 | * |
||
491 | * @return OrderInterface[] |
||
492 | */ |
||
493 | public function orders() |
||
494 | { |
||
495 | return $this->source()->orders(); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Alias of {@see SourceInterface::hasOrders()} |
||
500 | * |
||
501 | * @return boolean |
||
502 | */ |
||
503 | public function hasOrders() |
||
504 | { |
||
505 | return $this->source()->hasOrders(); |
||
506 | } |
||
507 | |||
508 | /** |
||
509 | * Alias of {@see SourceInterface::setOrders()} |
||
510 | * |
||
511 | * @param array $orders An array of orders. |
||
512 | * @return self |
||
513 | */ |
||
514 | public function setOrders(array $orders) |
||
515 | { |
||
516 | $this->source()->setOrders($orders); |
||
517 | return $this; |
||
518 | } |
||
519 | |||
520 | /** |
||
521 | * Alias of {@see SourceInterface::addOrders()} |
||
522 | * |
||
523 | * @param array $orders An array of orders. |
||
524 | * @return self |
||
525 | */ |
||
526 | public function addOrders(array $orders) |
||
527 | { |
||
528 | foreach ($orders as $o) { |
||
529 | $this->addOrder($o); |
||
530 | } |
||
531 | return $this; |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * Alias of {@see SourceInterface::addOrder()} |
||
536 | * |
||
537 | * @param mixed $param The property to sort by, |
||
538 | * a {@see OrderInterface} object, |
||
539 | * or a order array structure. |
||
540 | * @param string $mode Optional sorting mode. |
||
541 | * Defaults to ascending if a property is provided. |
||
542 | * @param array $options Optional extra settings to apply on the order. |
||
543 | * @return self |
||
544 | */ |
||
545 | public function addOrder($param, $mode = 'asc', array $options = null) |
||
546 | { |
||
547 | $this->source()->addOrder($param, $mode, $options); |
||
548 | return $this; |
||
549 | } |
||
550 | |||
551 | /** |
||
552 | * Alias of {@see SourceInterface::pagination()} |
||
553 | * |
||
554 | * @return PaginationInterface |
||
555 | */ |
||
556 | public function pagination() |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * Alias of {@see SourceInterface::setPagination()} |
||
563 | * |
||
564 | * @param mixed $param An associative array of pagination settings. |
||
565 | * @return self |
||
566 | */ |
||
567 | public function setPagination($param) |
||
568 | { |
||
569 | $this->source()->setPagination($param); |
||
570 | |||
571 | return $this; |
||
572 | } |
||
573 | |||
574 | /** |
||
575 | * Alias of {@see PaginationInterface::page()} |
||
576 | * |
||
577 | * @return integer |
||
578 | */ |
||
579 | public function page() |
||
582 | } |
||
583 | |||
584 | /** |
||
585 | * Alias of {@see PaginationInterface::pagination()} |
||
586 | * |
||
587 | * @param integer $page A page number. |
||
588 | * @return self |
||
589 | */ |
||
590 | public function setPage($page) |
||
591 | { |
||
592 | $this->pagination()->setPage($page); |
||
593 | |||
594 | return $this; |
||
595 | } |
||
596 | |||
597 | /** |
||
598 | * Alias of {@see PaginationInterface::numPerPage()} |
||
599 | * |
||
600 | * @return integer |
||
601 | */ |
||
602 | public function numPerPage() |
||
603 | { |
||
604 | return $this->pagination()->numPerPage(); |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * Alias of {@see PaginationInterface::setNumPerPage()} |
||
609 | * |
||
610 | * @param integer $num The number of items to display per page. |
||
611 | * @return self |
||
612 | */ |
||
613 | public function setNumPerPage($num) |
||
614 | { |
||
615 | $this->pagination()->setNumPerPage($num); |
||
616 | |||
617 | return $this; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Set the callback routine applied to every object added to the collection. |
||
622 | * |
||
623 | * @param callable $callback The callback routine. |
||
624 | * @return self |
||
625 | */ |
||
626 | public function setCallback(callable $callback) |
||
627 | { |
||
628 | $this->callback = $callback; |
||
629 | |||
630 | return $this; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Retrieve the callback routine applied to every object added to the collection. |
||
635 | * |
||
636 | * @return callable|null |
||
637 | */ |
||
638 | public function callback() |
||
639 | { |
||
640 | return $this->callback; |
||
641 | } |
||
642 | |||
643 | /** |
||
644 | * Load a collection from source. |
||
645 | * |
||
646 | * @param string|null $ident Optional. A pre-defined list to use from the model. |
||
647 | * @param callable|null $callback Process each entity after applying raw data. |
||
648 | * Leave blank to use {@see CollectionLoader::callback()}. |
||
649 | * @param callable|null $before Process each entity before applying raw data. |
||
650 | * @throws Exception If the database connection fails. |
||
651 | * @return ModelInterface[]|ArrayAccess |
||
652 | */ |
||
653 | public function load($ident = null, callable $callback = null, callable $before = null) |
||
654 | { |
||
655 | // Unused. |
||
656 | unset($ident); |
||
657 | |||
658 | $query = $this->source()->sqlLoad(); |
||
659 | |||
660 | return $this->loadFromQuery($query, $callback, $before); |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Get the total number of items for this collection query. |
||
665 | * |
||
666 | * @throws RuntimeException If the database connection fails. |
||
667 | * @return integer |
||
668 | */ |
||
669 | public function loadCount() |
||
670 | { |
||
671 | $query = $this->source()->sqlLoadCount(); |
||
672 | |||
673 | $db = $this->source()->db(); |
||
674 | if (!$db) { |
||
675 | throw new RuntimeException( |
||
676 | 'Could not instanciate a database connection.' |
||
677 | ); |
||
678 | } |
||
679 | $this->logger->debug($query); |
||
680 | |||
681 | $sth = $db->prepare($query); |
||
682 | $sth->execute(); |
||
683 | $res = $sth->fetchColumn(0); |
||
684 | |||
685 | return (int)$res; |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Load list from query. |
||
690 | * |
||
691 | * **Example — Binding values to $query** |
||
692 | * |
||
693 | * ```php |
||
694 | * $this->loadFromQuery([ |
||
695 | * 'SELECT name, colour, calories FROM fruit WHERE calories < :calories AND colour = :colour', |
||
696 | * [ |
||
697 | * 'calories' => 150, |
||
698 | * 'colour' => 'red' |
||
699 | * ], |
||
700 | * [ 'calories' => PDO::PARAM_INT ] |
||
701 | * ]); |
||
702 | * ``` |
||
703 | * |
||
704 | * @param string|array $query The SQL query as a string or an array composed of the query, |
||
705 | * parameter binds, and types of parameter bindings. |
||
706 | * @param callable|null $callback Process each entity after applying raw data. |
||
707 | * Leave blank to use {@see CollectionLoader::callback()}. |
||
708 | * @param callable|null $before Process each entity before applying raw data. |
||
709 | * @throws RuntimeException If the database connection fails. |
||
710 | * @throws InvalidArgumentException If the SQL string/set is invalid. |
||
711 | * @return ModelInterface[]|ArrayAccess |
||
712 | */ |
||
713 | public function loadFromQuery($query, callable $callback = null, callable $before = null) |
||
714 | { |
||
715 | $db = $this->source()->db(); |
||
716 | |||
717 | if (!$db) { |
||
718 | throw new RuntimeException( |
||
719 | 'Could not instanciate a database connection.' |
||
720 | ); |
||
721 | } |
||
722 | |||
723 | /** @todo Filter binds */ |
||
724 | if (is_string($query)) { |
||
725 | $this->logger->debug($query); |
||
726 | $sth = $db->prepare($query); |
||
727 | $sth->execute(); |
||
728 | } elseif (is_array($query)) { |
||
729 | list($query, $binds, $types) = array_pad($query, 3, []); |
||
730 | $sth = $this->source()->dbQuery($query, $binds, $types); |
||
731 | } else { |
||
732 | throw new InvalidArgumentException(sprintf( |
||
733 | 'The SQL query must be a string or an array: '. |
||
734 | '[ string $query, array $binds, array $dataTypes ]; '. |
||
735 | 'received %s', |
||
736 | is_object($query) ? get_class($query) : $query |
||
737 | )); |
||
738 | } |
||
739 | |||
740 | $sth->setFetchMode(PDO::FETCH_ASSOC); |
||
741 | |||
742 | if ($callback === null) { |
||
743 | $callback = $this->callback(); |
||
744 | } |
||
745 | |||
746 | return $this->processCollection($sth, $before, $callback); |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * Process the collection of raw data. |
||
751 | * |
||
752 | * @param mixed[]|Traversable $results The raw result set. |
||
753 | * @param callable|null $before Process each entity before applying raw data. |
||
754 | * @param callable|null $after Process each entity after applying raw data. |
||
755 | * @return ModelInterface[]|ArrayAccess |
||
756 | */ |
||
757 | protected function processCollection($results, callable $before = null, callable $after = null) |
||
758 | { |
||
759 | $collection = $this->createCollection(); |
||
760 | foreach ($results as $objData) { |
||
761 | $obj = $this->processModel($objData, $before, $after); |
||
762 | |||
763 | if ($obj instanceof ModelInterface) { |
||
764 | $collection[] = $obj; |
||
765 | } |
||
766 | } |
||
767 | |||
768 | return $collection; |
||
769 | } |
||
770 | |||
771 | /** |
||
772 | * Process the raw data for one model. |
||
773 | * |
||
774 | * @param mixed $objData The raw dataset. |
||
775 | * @param callable|null $before Process each entity before applying raw data. |
||
776 | * @param callable|null $after Process each entity after applying raw data. |
||
777 | * @return ModelInterface|ArrayAccess|null |
||
778 | */ |
||
779 | protected function processModel($objData, callable $before = null, callable $after = null) |
||
794 | } |
||
795 | |||
796 | /** |
||
797 | * Create a collection class or array. |
||
798 | * |
||
799 | * @throws RuntimeException If the collection class is invalid. |
||
800 | * @return array|ArrayAccess |
||
801 | */ |
||
802 | public function createCollection() |
||
803 | { |
||
804 | $collectClass = $this->collectionClass(); |
||
805 | if ($collectClass === 'array') { |
||
806 | return []; |
||
807 | } |
||
808 | |||
809 | if (!class_exists($collectClass)) { |
||
810 | throw new RuntimeException(sprintf( |
||
811 | 'Collection class [%s] does not exist.', |
||
812 | $collectClass |
||
813 | )); |
||
814 | } |
||
815 | |||
816 | if (!is_subclass_of($collectClass, ArrayAccess::class)) { |
||
817 | throw new RuntimeException(sprintf( |
||
818 | 'Collection class [%s] must implement ArrayAccess.', |
||
819 | $collectClass |
||
820 | )); |
||
821 | } |
||
822 | |||
823 | $collection = new $collectClass; |
||
824 | |||
825 | return $collection; |
||
826 | } |
||
827 | |||
828 | /** |
||
829 | * Set the class name of the collection. |
||
830 | * |
||
831 | * @param string $className The class name of the collection. |
||
832 | * @throws InvalidArgumentException If the class name is not a string. |
||
833 | * @return self |
||
834 | */ |
||
835 | public function setCollectionClass($className) |
||
836 | { |
||
837 | if (!is_string($className)) { |
||
838 | throw new InvalidArgumentException( |
||
839 | 'Collection class name must be a string.' |
||
840 | ); |
||
841 | } |
||
842 | |||
843 | $this->collectionClass = $className; |
||
844 | |||
845 | return $this; |
||
846 | } |
||
847 | |||
848 | /** |
||
849 | * Retrieve the class name of the collection. |
||
850 | * |
||
851 | * @return string |
||
852 | */ |
||
853 | public function collectionClass() |
||
854 | { |
||
855 | return $this->collectionClass; |
||
856 | } |
||
857 | |||
858 | /** |
||
859 | * Allow an object to define how the key getter are called. |
||
860 | * |
||
861 | * @param string $key The key to get the getter from. |
||
862 | * @return string The getter method name, for a given key. |
||
863 | */ |
||
864 | protected function getter($key) |
||
865 | { |
||
866 | $getter = $key; |
||
867 | return $this->camelize($getter); |
||
868 | } |
||
869 | |||
870 | /** |
||
871 | * Allow an object to define how the key setter are called. |
||
872 | * |
||
873 | * @param string $key The key to get the setter from. |
||
874 | * @return string The setter method name, for a given key. |
||
875 | */ |
||
876 | protected function setter($key) |
||
877 | { |
||
878 | $setter = 'set_'.$key; |
||
879 | return $this->camelize($setter); |
||
880 | } |
||
881 | |||
882 | /** |
||
883 | * Transform a snake_case string to camelCase. |
||
884 | * |
||
885 | * @param string $str The snake_case string to camelize. |
||
886 | * @return string The camelcase'd string. |
||
887 | */ |
||
888 | protected function camelize($str) |
||
891 | } |
||
892 | } |
||
893 |