GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Branch master (9e3162)
by Dave
63:34
created

Elements   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 724
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 201
dl 0
loc 724
rs 2
c 0
b 0
f 0
wmc 85

40 Methods

Rating   Name   Duplication   Size   Complexity  
A forEachElement() 0 4 2
A setInstance() 0 3 1
A makeValidationAttribute() 0 3 1
A safeCreateModel() 0 3 1
A getRelation() 0 3 1
A getRequestData() 0 3 2
A getNewElements() 0 3 1
A formatElementName() 0 3 1
A getEmptyRelation() 0 3 2
A getModelClassForElements() 0 3 1
A setLimit() 0 5 1
A getValidationRulesFromElements() 0 7 1
A checkRelationOfModel() 0 11 4
A getValidationMessagesForElements() 0 7 1
A setLabel() 0 5 1
A afterSave() 0 16 2
A safeFillModel() 0 16 4
A buildGroupsCollection() 0 11 3
A initialize() 0 10 2
A setModel() 0 7 2
A addOrGetRelated() 0 9 4
A disableCreation() 0 5 1
A modifyValidationParameters() 0 8 2
A emptyElement() 0 21 3
A getCompositeKey() 0 15 4
B createGroup() 0 30 8
A toArray() 0 12 1
A setRelationName() 0 5 1
A initializeRemoveEntities() 0 13 2
A exceedsLimit() 0 7 2
A initializeElements() 0 9 3
A modifyQuery() 0 5 1
A getRelatedValuesFromRequestData() 0 27 5
A setGroupLabel() 0 5 1
A getElementValue() 0 19 3
A __construct() 0 9 1
A flatNamedElements() 0 13 3
A save() 0 6 1
A cloneElements() 0 8 1
A loadRelationValues() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like Elements 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 Elements, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SleepingOwl\Admin\Form\Related;
4
5
use DB;
6
use Illuminate\Http\Request;
7
use Admin\Contracts\HasFakeModel;
8
use Illuminate\Support\Collection;
9
use Illuminate\Database\Eloquent\Model;
10
use SleepingOwl\Admin\Form\FormElements;
11
use KodiComponents\Support\HtmlAttributes;
12
use SleepingOwl\Admin\Contracts\Initializable;
13
use SleepingOwl\Admin\Form\Element\NamedFormElement;
14
use Illuminate\Database\Eloquent\ModelNotFoundException;
15
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
16
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
17
use Illuminate\Database\Eloquent\RelationNotFoundException;
18
19
abstract class Elements extends FormElements
20
{
21
    use HtmlAttributes, HasUniqueValidation, ManipulatesRequestRelations;
22
23
    protected $view = 'form.element.related.elements';
24
25
    const NEW_ITEM = 'new';
26
27
    const REMOVE = 'remove';
28
29
    /**
30
     * How many items can be created.
31
     *
32
     * @var int
33
     */
34
    protected $limit;
35
36
    /**
37
     * Relation name of the model.
38
     *
39
     * @var string
40
     */
41
    protected $relationName;
42
43
    /**
44
     * New relations counter.
45
     *
46
     * @var int
47
     */
48
    protected $new = 0;
49
50
    /**
51
     * @var string
52
     */
53
    protected $groupLabel;
54
55
    /**
56
     * Main label of dynamic form.
57
     *
58
     * @var string
59
     */
60
    protected $label;
61
62
    /**
63
     * Loaded related values.
64
     *
65
     * @var \Illuminate\Support\Collection
66
     */
67
    protected $relatedValues;
68
69
    /**
70
     * @var \Illuminate\Database\Eloquent\Model
71
     */
72
    protected $instance;
73
74
    protected $stubElements;
75
76
    /**
77
     * Elements that are about to be removed.
78
     *
79
     * @var \Illuminate\Support\Collection
80
     */
81
    protected $toRemove;
82
83
    protected $emptyRelation;
84
85
    /**
86
     * @var
87
     */
88
    protected $groups;
89
90
    protected $queryCallbacks = [];
91
92
    public function __construct($relationName, array $elements = [])
93
    {
94
        $this->toRemove = collect();
95
        $this->groups = collect();
96
        $this->relatedValues = collect();
97
        parent::__construct($elements);
98
99
        $this->setRelationName($relationName);
100
        $this->initializeRemoveEntities();
101
    }
102
103
    /**
104
     * @param int $limit
105
     *
106
     * @return $this
107
     */
108
    public function setLimit($limit)
109
    {
110
        $this->limit = $limit;
111
112
        return $this;
113
    }
114
115
    /**
116
     * Adds query modifier callback for related values select. Here you may define your ordering, etc.
117
     *
118
     * @param callable $callback
119
     *
120
     * @return $this
121
     */
122
    public function modifyQuery(callable $callback)
123
    {
124
        $this->queryCallbacks[] = $callback;
125
126
        return $this;
127
    }
128
129
    /**
130
     * Sets the label of related form.
131
     *
132
     * @param $label
133
     *
134
     * @return $this
135
     */
136
    public function setLabel($label)
137
    {
138
        $this->label = $label;
139
140
        return $this;
141
    }
142
143
    public function initialize()
144
    {
145
        parent::initialize();
146
        $this->checkRelationOfModel();
147
148
        $this->stubElements = $this->getNewElements();
149
        $this->forEachElement($this->stubElements, function ($element) {
150
            $element->setDefaultValue(null);
151
            if (! $element instanceof HasFakeModel) {
152
                $element->setPath('');
153
            }
154
        });
155
    }
156
157
    protected function initializeRemoveEntities()
158
    {
159
        $key = $this->relationName.'.'.static::REMOVE;
160
        $newKey = 'remove_'.$this->relationName;
161
        $request = request();
162
163
        $remove = $request->input($key, $request->old($key, []));
164
        if ($remove) {
165
            $request->merge([$newKey => $remove]);
166
        }
167
168
        $this->toRemove = collect($request->input($newKey, $request->old($newKey, [])));
169
        $request->replace($request->except($key));
170
    }
171
172
    /**
173
     * @return void
174
     */
175
    public function initializeElements()
176
    {
177
        $this->getElements()->each(function ($element) {
178
            if ($element instanceof Initializable) {
179
                $element->initialize();
180
            }
181
182
            if ($element instanceof HasFakeModel) {
183
                $element->setFakeModel($this->getModel());
184
            }
185
        });
186
    }
187
188
    /**
189
     * @param $item
190
     *
191
     * @param array $columns
192
     *
193
     * @return string
194
     */
195
    protected function getCompositeKey($item, array $columns): string
196
    {
197
        $primaries = [];
198
        if ($item instanceof Model) {
199
            $item = $item->getAttributes();
200
        }
201
202
        foreach ($columns as $name) {
203
            // Only existing keys must be in primaries array
204
            if (array_key_exists($name, $item)) {
205
                $primaries[] = $item[$name];
206
            }
207
        }
208
209
        return implode('_', $primaries);
210
    }
211
212
    protected function makeValidationAttribute($name)
213
    {
214
        return $this->relationName.'.*.'.$name;
215
    }
216
217
    protected function getNewElements()
218
    {
219
        return $this->cloneElements($this);
220
    }
221
222
    protected function cloneElements(FormElements $element)
223
    {
224
        $elements = clone $element->getElements()->map(function ($element) {
225
            return clone $element;
226
        });
227
228
        return $elements->map(function ($element) {
229
            return $this->emptyElement($element);
230
        });
231
    }
232
233
    protected function emptyElement($element)
234
    {
235
        $el = clone $element;
236
        if ($el instanceof \SleepingOwl\Admin\Form\Columns\Columns) {
237
            $col = new Columns();
238
            $columns = $el->getElements();
239
            $col->setElements((clone $columns)->map(function ($column) {
240
                return $this->emptyElement($column);
241
            })->all());
242
243
            return $col;
244
        }
245
246
        if ($el instanceof FormElements) {
247
            $el->setElements($this->cloneElements($el)->all());
248
        } else {
249
            $el->setDefaultValue(null);
250
            $el->setValueSkipped(true);
251
        }
252
253
        return $el;
254
    }
255
256
    public function setModel(Model $model)
257
    {
258
        parent::setModel($model);
259
260
        if ($model->exists) {
261
            $this->setInstance($model);
262
            $this->loadRelationValues();
263
        }
264
    }
265
266
    public function setInstance($instance)
267
    {
268
        $this->instance = $instance;
269
    }
270
271
    protected function checkRelationOfModel()
272
    {
273
        $model = $this->getModel();
274
        $class = get_class($model);
275
        if (! method_exists($model, $this->relationName)) {
276
            throw new RelationNotFoundException("Relation {$this->relationName} doesn't exist on {$class}");
277
        }
278
279
        $relation = $model->{$this->relationName}();
280
        if (! ($relation instanceof BelongsToMany) && ! ($relation instanceof HasOneOrMany)) {
281
            throw new \InvalidArgumentException("Relation {$this->relationName} of model {$class} must be instance of HasMany or BelongsToMany");
282
        }
283
    }
284
285
    /**
286
     * Sets relation name property.
287
     *
288
     * @param string
289
     *
290
     * @return $this
291
     */
292
    public function setRelationName($name)
293
    {
294
        $this->relationName = $name;
295
296
        return $this;
297
    }
298
299
    protected function loadRelationValues()
300
    {
301
        if (! $this->instance) {
302
            throw new ModelNotFoundException("Model {$this->getModel()} wasn't found for loading relation");
303
        }
304
305
        $query = $this->getRelation();
306
        if (count($this->queryCallbacks) > 0) {
307
            foreach ($this->queryCallbacks as $callback) {
308
                $callback($query);
309
            }
310
        }
311
312
        $this->relatedValues = $this->retrieveRelationValuesFromQuery($query);
313
    }
314
315
    /**
316
     * @param \Illuminate\Http\Request|null $request
317
     *
318
     * @return array
319
     */
320
    protected function getRequestData(Request $request = null)
321
    {
322
        return $request ? $request->get($this->relationName, []) : old($this->relationName, []);
323
    }
324
325
    protected function buildGroupsCollection()
326
    {
327
        $relatedValues = $this->relatedValues;
328
329
        if (count($old = $this->getRequestData()) !== 0) {
330
            $relatedValues = $this->getRelatedValuesFromRequestData($old);
331
            $old = true;
332
        }
333
334
        foreach ($relatedValues as $key => $item) {
335
            $this->groups->push($this->createGroup($item, $old, $key));
0 ignored issues
show
Bug introduced by
It seems like $old can also be of type array; however, parameter $old of SleepingOwl\Admin\Form\R...Elements::createGroup() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
            $this->groups->push($this->createGroup($item, /** @scrutinizer ignore-type */ $old, $key));
Loading history...
336
        }
337
    }
338
339
    protected function getRelatedValuesFromRequestData(array $values)
340
    {
341
        $collection = collect();
342
        foreach ($values as $key => $attributes) {
343
            if ($key === static::REMOVE) {
344
                // If key is about to be removed we need to save it and show later in rendered form. But we don't
345
                // need to put value with this relation in collection of elements, that's why we need to continue the
346
                // loop
347
                $this->toRemove = collect($attributes);
348
                continue;
349
            }
350
351
            if (strpos($key, static::NEW_ITEM) !== false) {
352
                // If item is new wee need to implement counter of new items to prevent duplicates,
353
                // check limits and etc.
354
                $this->new++;
355
            }
356
357
            if ($this->relatedValues->has($key)) {
358
                $attributes = $this->safeFillModel($this->relatedValues->get($key), $attributes);
359
            }
360
361
            // Finally, we put filled model values into collection of future groups
362
            $collection->put($key, $attributes);
363
        }
364
365
        return $collection;
366
    }
367
368
    /**
369
     * Creates new group of relation and returns it.
370
     *
371
     * @param array|Model $attributes Attributes of one group (relation)
372
     * @param bool $old Is it old data from previous request after validation error or something like that
373
     * @param null $key Key of attributes
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
374
     *
375
     * @return \SleepingOwl\Admin\Form\Related\Group
376
     */
377
    protected function createGroup($attributes, $old = false, $key = null)
378
    {
379
        $model = $attributes instanceof Model ? $attributes : $this->safeCreateModel($this->getModelClassForElements(), $attributes);
380
        $group = new Group($model);
381
382
        if ($this->groupLabel) {
383
            $group->setLabel($this->groupLabel);
384
        }
385
386
        $this->forEachElement($elements = $this->getNewElements(), function (NamedFormElement $el) use ($model, $key, $old) {
387
            // Setting default value, name and model for element with name attribute
388
            $el->setDefaultValue($el->prepareValue($this->getElementValue($model, $el)));
389
            $el->setName(sprintf('%s[%s][%s]', $this->relationName, $key ?: $model->getKey(), $this->formatElementName($el->getName())));
390
            $el->setModel($model);
391
392
            if ($old && strpos($el->getPath(), '->') === false && ! ($el instanceof HasFakeModel)) {
393
                // If there were old values (validation fail, etc.) each element must have different path to get the old
394
                // value. If we don't do it, there will be collision if two elements with same name present in main form
395
                // and related form. For example: we have "Company" and "Shop" models with field "name" and include HasMany
396
                // form with company's shops inside "Companies" section. There will be collisions of "name" if validation
397
                // fails, and each "shop"-form will have "company->name" value inside "name" field.
398
                $el->setPath($el->getName());
399
            }
400
        });
401
402
        foreach ($elements as $el) {
403
            $group->push($el);
404
        }
405
406
        return $group;
407
    }
408
409
    /**
410
     * Returns value from model for given element.
411
     *
412
     * @param \Illuminate\Database\Eloquent\Model $model
413
     * @param \SleepingOwl\Admin\Form\Element\NamedFormElement $el
414
     *
415
     * @return mixed|null
416
     */
417
    protected function getElementValue(Model $model, NamedFormElement $el)
418
    {
419
        $attribute = $el->getModelAttributeKey();
420
        if (strpos($attribute, '->') === false) {
421
            return $model->getAttribute($attribute);
422
        }
423
424
        // Parse json attributes
425
        $casts = collect($model->getCasts());
426
        $jsonParts = collect(explode('->', $attribute));
427
        $cast = $casts->get($jsonParts->first(), false);
428
429
        if (! in_array($cast, ['json', 'array'])) {
430
            return;
431
        }
432
433
        $jsonAttr = $model->{$jsonParts->first()};
434
435
        return array_get($jsonAttr, $jsonParts->slice(1)->implode('.'));
436
    }
437
438
    /**
439
     * Replaces element name to key of entity.
440
     *
441
     * @param string $name
442
     *
443
     * @return null|string|string[]
444
     */
445
    protected function formatElementName($name)
446
    {
447
        return preg_replace("/{$this->relationName}\[[\w]+\]\[(.+?)\]/", '$1', $name);
448
    }
449
450
    /**
451
     * Applies given callback to every element of form.
452
     *
453
     * @param \Illuminate\Support\Collection $elements
454
     * @param $callback
455
     */
456
    protected function forEachElement(Collection $elements, $callback)
457
    {
458
        foreach ($this->flatNamedElements($elements) as $element) {
459
            $callback($element);
460
        }
461
    }
462
463
    /**
464
     * Returns flat collection of elements in form ignoring everything but NamedFormElement. Works recursive.
465
     *
466
     * @param \Illuminate\Support\Collection $elements
467
     *
468
     * @return mixed
469
     */
470
    protected function flatNamedElements(Collection $elements)
471
    {
472
        return $elements->reduce(function (Collection $initial, $element) {
473
            if ($element instanceof NamedFormElement) {
474
                // Is it what we're loogin for? if so we'll push it to final collection
475
                $initial->push($element);
476
            } elseif ($element instanceof FormElements) {
477
                // Go deeper and repeat everything again
478
                return $initial->merge($this->flatNamedElements($element->getElements()));
479
            }
480
481
            return $initial;
482
        }, collect());
483
    }
484
485
    /**
486
     * Creates new model of given class and calls fill on it.
487
     *
488
     * @param $modelClass
489
     * @param array $attributes
490
     *
491
     * @return \Illuminate\Database\Eloquent\Model
492
     */
493
    protected function safeCreateModel($modelClass, array $attributes = [])
494
    {
495
        return $this->safeFillModel(new $modelClass, $attributes);
496
    }
497
498
    /**
499
     * Fills given model with given attributes using setAttributes, not batch fill
500
     * to prevent guard errors of model attributes.
501
     *
502
     * @param \Illuminate\Database\Eloquent\Model $model
503
     * @param array $attributes
504
     *
505
     * @return \Illuminate\Database\Eloquent\Model
506
     */
507
    protected function safeFillModel(Model $model, array $attributes = [])
508
    {
509
        foreach ($attributes as $attribute => $value) {
510
            // Prevent numeric attribute name. If it is, so it's an error
511
            if (is_numeric($attribute)) {
512
                continue;
513
            }
514
515
            try {
516
                $model->setAttribute($attribute, $value);
517
            } catch (\Exception $exception) {
518
                // Ignore attribute set exception
519
            }
520
        }
521
522
        return $model;
523
    }
524
525
    /**
526
     * Returns empty relation of model.
527
     *
528
     * @return \Illuminate\Database\Eloquent\Relations\Relation
529
     */
530
    protected function getEmptyRelation()
531
    {
532
        return $this->emptyRelation ?: $this->emptyRelation = $this->getModel()->{$this->relationName}();
533
    }
534
535
    /**
536
     * Returns relation of current instance.
537
     *
538
     * @return mixed
539
     */
540
    protected function getRelation()
541
    {
542
        return $this->instance->{$this->relationName}();
543
    }
544
545
    /**
546
     * Saves request.
547
     *
548
     * @param \Illuminate\Http\Request $request
549
     */
550
    public function save(Request $request)
551
    {
552
        $this->prepareRelatedValues($this->getRequestData($request));
553
554
        $this->transactionLevel = DB::transactionLevel();
0 ignored issues
show
Bug Best Practice introduced by
The property transactionLevel does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
555
        DB::beginTransaction();
556
        // Nothing to do here...
557
    }
558
559
    /**
560
     * @param array $rules
561
     *
562
     * @return array
563
     */
564
    public function getValidationRulesFromElements(array $rules = [])
565
    {
566
        $this->flatNamedElements($this->getElements())->each(function ($element) use (&$rules) {
567
            $rules += $this->modifyValidationParameters($element->getValidationRules());
568
        });
569
570
        return $rules;
571
    }
572
573
    public function getValidationMessagesForElements(array $messages = [])
574
    {
575
        $this->flatNamedElements($this->getElements())->each(function ($element) use (&$messages) {
576
            $messages += $this->modifyValidationParameters($element->getValidationMessages());
577
        });
578
579
        return $messages;
580
    }
581
582
    public function afterSave(Request $request)
583
    {
584
        try {
585
            // By this time getModel method will always return existed model object, not empty
586
            // so wee need to fresh it, because if it's new model creating relation will throw
587
            // exception 'call relation method on null'
588
            $this->setInstance($this->getModel());
589
            $this->proceedSave($request);
590
            DB::commit();
591
592
            $this->prepareRequestToBeCopied($request);
593
        } catch (\Throwable $exception) {
594
            \Session::flash('success_message', 'Произошла ошибка сохранения');
595
            DB::rollBack($this->transactionLevel);
0 ignored issues
show
Unused Code introduced by
The call to Illuminate\Support\Facades\DB::rollBack() has too many arguments starting with $this->transactionLevel. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

595
            DB::/** @scrutinizer ignore-call */ 
596
                rollBack($this->transactionLevel);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
596
597
            throw $exception;
598
        }
599
    }
600
601
    /**
602
     * Returns model class for each element in form.
603
     *
604
     * @return string
605
     */
606
    protected function getModelClassForElements()
607
    {
608
        return get_class($this->getModelForElements());
609
    }
610
611
    /**
612
     * Modifies validation parameters appending asterisk (*) to every field. We need this stuff because we're creating
613
     * grouped forms here, you know :).
614
     *
615
     * @param array $parameters
616
     *
617
     * @return array
618
     */
619
    protected function modifyValidationParameters(array $parameters)
620
    {
621
        $result = [];
622
        foreach ($parameters as $name => $parameter) {
623
            $result["{$this->relationName}.*.{$name}"] = $parameter;
624
        }
625
626
        return $result;
627
    }
628
629
    /**
630
     * Get the instance as an array.
631
     *
632
     * @return array
633
     */
634
    public function toArray()
635
    {
636
        $this->buildGroupsCollection();
637
638
        return parent::toArray() + [
639
                'stub'             => $this->stubElements,
640
                'name'             => $this->relationName,
641
                'label'            => $this->label,
642
                'groups'           => $this->groups,
643
                'remove'           => $this->toRemove,
644
                'newEntitiesCount' => $this->new,
645
                'limit'            => $this->limit,
646
            ];
647
    }
648
649
    /**
650
     * @param string $groupLabel
651
     *
652
     * @return $this
653
     */
654
    public function setGroupLabel($groupLabel)
655
    {
656
        $this->groupLabel = $groupLabel;
657
658
        return $this;
659
    }
660
661
    /**
662
     * Checks if count of limit is exceeded.
663
     *
664
     * @return int
665
     */
666
    public function exceedsLimit()
667
    {
668
        if ($this->limit === null) {
669
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
670
        }
671
672
        return $this->relatedValues->count() >= $this->limit;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->relatedVal...count() >= $this->limit returns the type boolean which is incompatible with the documented return type integer.
Loading history...
673
    }
674
675
    /**
676
     * Appends fresh related model if total count is not exceeding limit.
677
     *
678
     * @param $key
679
     *
680
     * @return Model
681
     */
682
    protected function addOrGetRelated($key)
683
    {
684
        $related = $this->relatedValues->get($key) ?: $this->getFreshModelForElements();
685
686
        if (! $related->exists && ! $this->exceedsLimit()) {
687
            $this->relatedValues->put($key, $related);
688
        }
689
690
        return $related;
691
    }
692
693
    /**
694
     * @return $this
695
     */
696
    public function disableCreation()
697
    {
698
        $this->setLimit(0);
699
700
        return $this;
701
    }
702
703
    /**
704
     * Retrieves related values from given query.
705
     *
706
     * @param $query
707
     *
708
     * @return Collection
709
     */
710
    abstract protected function retrieveRelationValuesFromQuery($query);
711
712
    /**
713
     * Returns model for each element in form.
714
     *
715
     * @return \Illuminate\Database\Eloquent\Model
716
     */
717
    abstract protected function getModelForElements();
718
719
    /**
720
     * Returns fresh instance of model for each element in form.
721
     *
722
     * @return \Illuminate\Database\Eloquent\Model
723
     */
724
    abstract protected function getFreshModelForElements();
725
726
    /**
727
     * Proceeds saving related values after all validations passes.
728
     *
729
     * @param \Illuminate\Http\Request $request
730
     *
731
     * @return mixed
732
     */
733
    abstract protected function proceedSave(Request $request);
734
735
    /**
736
     * Here you must add all new relations to main collection and etc.
737
     *
738
     * @param array $data
739
     *
740
     * @return mixed
741
     */
742
    abstract protected function prepareRelatedValues(array $data);
743
}
744