Passed
Push — master ( ac1a30...a17713 )
by Matthew
11:31 queued 09:02
created

Mapper::objectUpToDate()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 11
c 2
b 0
f 0
dl 0
loc 18
rs 9.2222
cc 6
nc 4
nop 5
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\ORM\SalsifyIDExtension;
6
use Dynamic\Salsify\Task\ImportTask;
7
use Exception;
8
use JsonMachine\JsonMachine;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\HasManyList;
13
use SilverStripe\ORM\ManyManyList;
14
use SilverStripe\Versioned\Versioned;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Versioned\Versioned was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
16
/**
17
 * Class Mapper
18
 * @package Dynamic\Salsify\Model
19
 */
20
class Mapper extends Service
21
{
22
23
    /**
24
     * @var bool
25
     */
26
    public static $SINGLE = false;
27
28
    /**
29
     * @var bool
30
     */
31
    public static $MULTIPLE = true;
32
33
    /**
34
     * @var
35
     */
36
    private $file = null;
37
38
    /**
39
     * @var JsonMachine
40
     */
41
    private $productStream;
42
43
    /**
44
     * @var JsonMachine
45
     */
46
    private $assetStream;
47
48
    /**
49
     * @var array
50
     */
51
    private $currentUniqueFields = [];
52
53
    /**
54
     * @var int
55
     */
56
    private $importCount = 0;
57
58
    /**
59
     * @var bool
60
     */
61
    public $skipSilently = false;
62
63
    /**
64
     * Mapper constructor.
65
     * @param string $importerKey
66
     * @param $file
67
     * @throws \Exception
68
     */
69
    public function __construct($importerKey, $file = null)
70
    {
71
        parent::__construct($importerKey);
72
        if (!$this->config()->get('mapping')) {
73
            throw  new Exception('A Mapper needs a mapping');
74
        }
75
76
        if ($file !== null) {
77
            $this->file = $file;
78
            $this->resetProductStream();
79
            $this->resetAssetStream();
80
        }
81
    }
82
83
    /**
84
     *
85
     */
86
    public function resetProductStream()
87
    {
88
        $this->productStream = JsonMachine::fromFile($this->file, '/4/products');
89
    }
90
91
    /**
92
     *
93
     */
94
    public function resetAssetStream()
95
    {
96
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
97
    }
98
99
    /**
100
     * Maps the data
101
     * @throws \Exception
102
     */
103
    public function map()
104
    {
105
        $this->extend('onBeforeMap', $this->file, Mapper::$MULTIPLE);
106
107
        foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
108
            foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
109
                $this->mapToObject($class, $mappings, $data);
110
                $this->currentUniqueFields = [];
111
            }
112
        }
113
114
        if ($this->mappingHasSalsifyRelation()) {
115
            ImportTask::output("----------------");
116
            ImportTask::output("Setting up salsify relations");
117
            ImportTask::output("----------------");
118
            $this->resetProductStream();
119
120
            foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
121
                foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
122
                    $this->mapToObject($class, $mappings, $data, null, true);
123
                    $this->currentUniqueFields = [];
124
                }
125
            }
126
        }
127
128
        ImportTask::output("Imported and updated $this->importCount products.");
129
        $this->extend('onAfterMap', $this->file, Mapper::$MULTIPLE);
130
    }
131
132
    /**
133
     * @param string|DataObject $class
134
     * @param array $mappings The mapping for a specific class
135
     * @param array $data
136
     * @param DataObject|null $object
137
     * @param bool $salsifyRelations
138
     * @param bool $forceUpdate
139
     *
140
     * @return DataObject|null
141
     * @throws \Exception
142
     */
143
    public function mapToObject(
144
        $class,
145
        $mappings,
146
        $data,
147
        $object = null,
148
        $salsifyRelations = false,
149
        $forceUpdate = false
150
    )
151
    {
152
        if ($salsifyRelations) {
153
            if (!$this->classConfigHasSalsifyRelation($mappings)) {
154
                return null;
155
            }
156
        }
157
158
        // if object was not passed
159
        if ($object === null) {
160
            $object = $this->findObjectByUnique($class, $mappings, $data);
161
162
            $filter = $this->getUniqueFilter($class, $mappings, $data);
163
            if (count(array_filter($filter)) == 0) {
164
                return null;
165
            }
166
167
            // if no existing object was found but a unique filter is valid (not empty)
168
            if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
169
                $object = $class::create();
170
            }
171
        }
172
173
        $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false;
174
        $wasWritten = $object->isInDB();
175
176
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
177
        if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) {
178
            $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
179
        } else {
180
            $firstUniqueValue = 'NULL';
181
        }
182
        ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue");
183
184
        if (
185
            !$forceUpdate &&
186
            $this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations)
187
        ) {
188
            return $object;
189
        }
190
191
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
192
            $field = $this->getField($salsifyField, $data);
193
            if ($field === false) {
194
                $this->clearValue($object, $dbField, $salsifyField);
195
                continue;
196
            }
197
198
            $type = $this->getFieldType($salsifyField);
199
            if ($salsifyRelations && ($type != 'SalsifyRelation' && $type != 'SalsifyRelationTimeStamp')) {
200
                continue;
201
            }
202
203
            $value = null;
204
            $objectData = $data;
0 ignored issues
show
Unused Code introduced by
The assignment to $objectData is dead and can be removed.
Loading history...
205
206
            if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) {
207
                if (!$this->skipSilently) {
208
                    ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue");
209
                    $this->skipSilently = false;
210
                }
211
                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 SilverStripe\ORM\DataObject|null.
Loading history...
212
            };
213
214
            $objectData = $this->handleModification($class, $dbField, $salsifyField, $data);
215
            $sortColumn = $this->getSortColumn($salsifyField);
216
217
            if ($salsifyRelations == false && !array_key_exists($field, $objectData)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
218
                continue;
219
            }
220
221
            $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField);
0 ignored issues
show
Bug introduced by
$type of type string is incompatible with the type integer expected by parameter $type of Dynamic\Salsify\Model\Mapper::handleType(). ( Ignorable by Annotation )

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

221
            $value = $this->handleType(/** @scrutinizer ignore-type */ $type, $class, $objectData, $field, $salsifyField, $dbField);
Loading history...
222
            $this->writeValue($object, $dbField, $value, $sortColumn);
223
        }
224
225
        if ($object->isChanged()) {
226
            $object->write();
227
            $this->importCount++;
228
            $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished);
229
        } else {
230
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
231
        }
232
        return $object;
233
    }
234
235
    /**
236
     * @param DataObject $object
237
     * @param array $data
238
     * @param string $firstUniqueKey
239
     * @param string $firstUniqueValue
240
     * @param bool $salsifyRelations
241
     * @return bool
242
     */
243
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false)
244
    {
245
        if ($this->config()->get('skipUpToDate') == true) {
246
            if (
247
                $salsifyRelations == false &&
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
248
                $object->hasField('SalsifyUpdatedAt') &&
249
                $data['salsify:updated_at'] == $object->getField('SalsifyUpdatedAt')
250
            ) {
251
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue. It is up to Date.");
252
                return true;
253
            }
254
255
            if ($this->objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations)) {
256
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue relations. It is up to Date.");
257
                return true;
258
            }
259
        }
260
        return false;
261
    }
262
263
    /**
264
     * @param DataObject $object
265
     * @param array $data
266
     * @param string $firstUniqueKey
267
     * @param string $firstUniqueValue
268
     * @param bool $salsifyRelations
269
     * @return bool
270
     */
271
    public function objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false)
272
    {
273
        if ($salsifyRelations != true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
274
            return true;
275
        }
276
277
        if (!$object->hasField('SalsifyRelationsUpdatedAt')) {
278
            return true;
279
        }
280
281
        // relations were never updated, so its up to date
282
        if (!isset($data['salsify:relations_updated_at'])) {
283
            return true;
284
        }
285
286
        if ($data['salsify:relations_updated_at'] != $object->getField('SalsifyRelationsUpdatedAt')) {
287
            return false;
288
        }
289
290
        return true;
291
    }
292
293
    /**
294
     * @param array $salsifyField
295
     * @param array $data
296
     *
297
     * @return string|false
298
     */
299
    private function getField($salsifyField, $data)
300
    {
301
        if (!is_array($salsifyField)) {
0 ignored issues
show
introduced by
The condition is_array($salsifyField) is always true.
Loading history...
302
            return array_key_exists($salsifyField, $data) ? $salsifyField : false;
303
        }
304
305
        $hasSalsifyField = array_key_exists('salsifyField', $salsifyField);
306
        $isLiteralField = (
307
            $this->getFieldType($salsifyField) === 'Literal' &&
308
            array_key_exists('value', $salsifyField)
309
        );
310
        $isSalsifyRelationField = (
311
            $this->getFieldType($salsifyField) === 'SalsifyRelation' &&
312
            $hasSalsifyField
313
        );
314
315
        if ($isLiteralField) {
316
            return $salsifyField['value'];
317
        }
318
319
        if ($isSalsifyRelationField) {
320
            return $salsifyField['salsifyField'];
321
        }
322
323
        if (!$hasSalsifyField) {
324
            return false;
325
        }
326
327
        if (array_key_exists($salsifyField['salsifyField'], $data)) {
328
            return $salsifyField['salsifyField'];
329
        } elseif (array_key_exists('fallback', $salsifyField)) {
330
            // make fallback an array
331
            if (!is_array($salsifyField['fallback'])) {
332
                $salsifyField['fallback'] = [$salsifyField['fallback']];
333
            }
334
335
            foreach ($this->yieldSingle($salsifyField['fallback']) as $fallback) {
336
                if (array_key_exists($fallback, $data)) {
337
                    return $fallback;
338
                }
339
            }
340
        } elseif (array_key_exists('modification', $salsifyField)) {
341
            return $salsifyField['salsifyField'];
342
        }
343
344
        return false;
345
    }
346
347
    /**
348
     * @param string $class
349
     * @param array $mappings
350
     * @param array $data
351
     *
352
     * @return array
353
     */
354
    private function getUniqueFilter($class, $mappings, $data)
355
    {
356
        $uniqueFields = $this->uniqueFields($class, $mappings);
357
        // creates a filter
358
        $filter = [];
359
        foreach ($this->yieldKeyVal($uniqueFields) as $dbField => $salsifyField) {
360
            $modifiedData = $data;
361
            $fieldMapping = $mappings[$dbField];
362
363
            $modifiedData = $this->handleModification($class, $dbField, $fieldMapping, $modifiedData);
364
365
            // adds unique fields to filter
366
            if (array_key_exists($salsifyField, $modifiedData)) {
367
                $filter[$dbField] = $modifiedData[$salsifyField];
368
            }
369
        }
370
371
        return $filter;
372
    }
373
374
    /**
375
     * @param string $class
376
     * @param array $mappings
377
     * @param array $data
378
     *
379
     * @return \SilverStripe\ORM\DataObject
380
     */
381
    private function findObjectByUnique($class, $mappings, $data)
382
    {
383
        if ($obj = $this->findBySalsifyID($class, $mappings, $data)) {
384
            return $obj;
385
        }
386
387
        $filter = $this->getUniqueFilter($class, $mappings, $data);
388
        return DataObject::get($class)->filter($filter)->first();
389
    }
390
391
    /**
392
     * @param string $class
393
     * @param array $mappings
394
     * @param array $data
395
     *
396
     * @return \SilverStripe\ORM\DataObject|bool
397
     */
398
    private function findBySalsifyID($class, $mappings, $data)
399
    {
400
        /** @var DataObject $genericObject */
401
        $genericObject = Injector::inst()->get($class);
402
        if (
403
            !$genericObject->hasExtension(SalsifyIDExtension::class) &&
404
            !$genericObject->hasField('SalsifyID')
405
        ) {
406
            return false;
407
        }
408
409
        $modifiedData = $data;
410
        if (array_key_exists('salsify:id', $mappings)) {
411
            $modifiedData = $this->handleModification($class, 'salsify:id', $mappings['salsify:id'], $modifiedData);
412
        }
413
        $obj = DataObject::get($class)->filter([
414
            'SalsifyID' => $modifiedData['salsify:id'],
415
        ])->first();
416
        if ($obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
417
            return $obj;
418
        }
419
420
        return false;
421
    }
422
423
    /**
424
     * Gets a list of all the unique field keys
425
     *
426
     * @param string class
0 ignored issues
show
Bug introduced by
The type Dynamic\Salsify\Model\class was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
427
     * @param array $mappings
428
     * @return array
429
     */
430
    private function uniqueFields($class, $mappings)
431
    {
432
        // cached after first map
433
        if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) {
434
            return $this->currentUniqueFields[$class];
435
        }
436
437
        $uniqueFields = [];
438
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
439
            if (!is_array($salsifyField)) {
440
                continue;
441
            }
442
443
            if (
444
                !array_key_exists('unique', $salsifyField) ||
445
                !array_key_exists('salsifyField', $salsifyField)
446
            ) {
447
                continue;
448
            }
449
450
            if ($salsifyField['unique'] !== true) {
451
                continue;
452
            }
453
454
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
455
        }
456
457
        $this->currentUniqueFields[$class] = $uniqueFields;
458
        return $uniqueFields;
459
    }
460
461
    /**
462
     * @param array|string $salsifyField
463
     * @return bool|mixed
464
     */
465
    private function getSortColumn($salsifyField)
466
    {
467
        if (!is_array($salsifyField)) {
468
            return false;
469
        }
470
471
        if (array_key_exists('sortColumn', $salsifyField)) {
472
            return $salsifyField['sortColumn'];
473
        }
474
475
        return false;
476
    }
477
478
    /**
479
     * @return bool
480
     */
481
    private function mappingHasSalsifyRelation()
482
    {
483
        foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
484
            if ($this->classConfigHasSalsifyRelation($mappings)) {
485
                return true;
486
            }
487
        }
488
        return false;
489
    }
490
491
    /**
492
     * @param array $classConfig
493
     * @return bool
494
     */
495
    private function classConfigHasSalsifyRelation($classConfig)
496
    {
497
        foreach ($this->yieldKeyVal($classConfig) as $field => $config) {
498
            if (!is_array($config)) {
499
                continue;
500
            }
501
502
            if (!array_key_exists('salsifyField', $config)) {
503
                continue;
504
            }
505
506
            if (!array_key_exists('type', $config)) {
507
                continue;
508
            }
509
510
            if ($config['type'] === 'SalsifyRelation') {
511
                return true;
512
            }
513
        }
514
        return false;
515
    }
516
517
    /**
518
     * @param string $class
519
     * @param string $dbField
520
     * @param array $config
521
     * @param array $data
522
     * @return array
523
     */
524
    private function handleModification($class, $dbField, $config, $data)
525
    {
526
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
527
            return $data;
528
        }
529
530
        if (array_key_exists('modification', $config)) {
531
            $mod = $config['modification'];
532
            if ($this->hasMethod($mod)) {
533
                return $this->{$mod}($class, $dbField, $config, $data);
534
            }
535
            ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
536
        }
537
        return $data;
538
    }
539
540
    /**
541
     * @param string $class
542
     * @param string $dbField
543
     * @param array $config
544
     * @param array $data
545
     * @return boolean
546
     */
547
    public function handleShouldSkip($class, $dbField, $config, $data)
548
    {
549
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
550
            return false;
551
        }
552
553
        if (array_key_exists('shouldSkip', $config)) {
554
            $skipMethod = $config['shouldSkip'];
555
            if ($this->hasMethod($skipMethod)) {
556
                return $this->{$skipMethod}($class, $dbField, $config, $data);
557
            }
558
            ImportTask::output(
559
                "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}."
560
            );
561
        }
562
        return false;
563
    }
564
565
    /**
566
     * @param string|array $field
567
     * @return string
568
     */
569
    public function getFieldType($field)
570
    {
571
        $fieldTypes = $this->config()->get('field_types');
572
        if (is_array($field) && array_key_exists('type', $field)) {
573
            if (in_array($field['type'], $fieldTypes)) {
574
                return $field['type'];
575
            }
576
        }
577
        // default to raw
578
        return 'Raw';
579
    }
580
581
    /**
582
     * @param int $type
583
     * @param string|DataObject $class
584
     * @param array $salsifyData
585
     * @param string $salsifyField
586
     * @param array $dbFieldConfig
587
     * @param string $dbField
588
     *
589
     * @return mixed
590
     */
591
    private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField)
592
    {
593
        if ($this->hasMethod("handle{$type}Type")) {
594
            return $this->{"handle{$type}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
595
        }
596
597
        if ($fallbacks = $this->config()->get('typeFallbacks')) {
598
            foreach ($fallbacks as $original => $fallback) {
599
                if ($type == $original) {
600
                    if ($this->hasMethod("handle{$fallback}Type")) {
601
                        return $this->{"handle{$fallback}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
602
                    }
603
                }
604
            }
605
        }
606
        ImportTask::output("{$type} is not a valid type. skipping field {$dbField}.");
607
        return '';
608
    }
609
610
    /**
611
     * @param DataObject $object
612
     * @param string $dbField
613
     * @param mixed $value
614
     * @param string|bool $sortColumn
615
     *
616
     * @throws \Exception
617
     */
618
    private function writeValue($object, $dbField, $value, $sortColumn)
619
    {
620
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
621
            array_key_exists($dbField, $object->config()->get('many_many')) ||
622
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
623
624
        $isSingleRelation = array_key_exists(rtrim($dbField, 'ID'), $object->config()->get('has_one'));
625
626
        if (!$isManyRelation) {
627
            if (!$isSingleRelation || ($isSingleRelation && $value !== false)) {
628
                $object->$dbField = $value;
629
            }
630
            return;
631
        }
632
633
        // change to an array and filter out empty values
634
        if (!is_array($value)) {
635
            $value = [$value];
636
        }
637
        $value = array_filter($value);
638
639
        // don't try to write an empty set
640
        if (!count($value)) {
641
            return;
642
        }
643
644
        // write the object so relations can be written
645
        if (!$object->exists()) {
646
            $object->write();
647
        }
648
649
        $this->removeUnrelated($object, $dbField, $value);
650
        $this->writeManyRelation($object, $dbField, $value, $sortColumn);
651
    }
652
653
    /**
654
     * @param DataObject $object
655
     * @param string $dbField
656
     * @param array $value
657
     * @param string|bool $sortColumn
658
     *
659
     * @throws \Exception
660
     */
661
    private function writeManyRelation($object, $dbField, $value, $sortColumn)
662
    {
663
        /** @var DataList|HasManyList|ManyManyList $relation */
664
        $relation = $object->{$dbField}();
665
666
        if ($sortColumn && $relation instanceof ManyManyList) {
667
            for ($i = 0; $i < count($value); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
668
                $relation->add($value[$i], [$sortColumn => $i]);
669
            }
670
            return;
671
        }
672
673
        // HasManyList, so it exists on the value
674
        if ($sortColumn) {
675
            for ($i = 0; $i < count($value); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
676
                $value[$i]->{$sortColumn} = $i;
677
                $relation->add($value[$i]);
678
            }
679
            return;
680
        }
681
682
        $relation->addMany($value);
683
    }
684
685
    /**
686
     * Removes unrelated objects in the relation that were previously related
687
     * @param DataObject $object
688
     * @param string $dbField
689
     * @param array $value
690
     */
691
    private function removeUnrelated($object, $dbField, $value)
692
    {
693
        $ids = [];
694
        foreach ($value as $v) {
695
            $ids[] = $v->ID;
696
        }
697
698
        /** @var DataList $relation */
699
        $relation = $object->{$dbField}();
700
        // remove all unrelated - removeAll had an odd side effect (relations only got added back half the time)
701
        if (!empty($ids)) {
702
            $relation->removeMany(
703
                $relation->exclude([
704
                    'ID' => $ids,
705
                ])->column('ID')
706
            );
707
        }
708
    }
709
710
    /**
711
     * @param string|array $salsifyField
712
     * @throws Exception
713
     */
714
    private function clearValue($object, $dbField, $salsifyField)
715
    {
716
        if (
717
            is_array($salsifyField) &&
718
            array_key_exists('keepExistingValue', $salsifyField) &&
719
            $salsifyField['keepExistingValue']
720
        ) {
721
            return;
722
        }
723
724
        // clear any existing value
725
        $this->writeValue($object, $dbField, null, null);
726
    }
727
728
    /**
729
     * @return \JsonMachine\JsonMachine
730
     */
731
    public function getAssetStream()
732
    {
733
        return $this->assetStream;
734
    }
735
736
    /**
737
     * @return bool
738
     */
739
    public function hasFile()
740
    {
741
        return $this->file !== null;
742
    }
743
}
744