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
|
|||||
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); |
||||
0 ignored issues
–
show
$importerKey of type string is incompatible with the type Dynamic\Salsify\Model\stirng expected by parameter $importerKey of Dynamic\Salsify\Model\Service::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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 | * Generates the current hash for the mapping |
||||
85 | * |
||||
86 | * @return string |
||||
87 | */ |
||||
88 | private function getMappingHash() |
||||
89 | { |
||||
90 | return md5(serialize($this->config()->get('mapping'))); |
||||
91 | } |
||||
92 | |||||
93 | /** |
||||
94 | * Updates the mapping hash for the mapper |
||||
95 | * @param DataObject|SalsifyIDExtension $object |
||||
96 | * @param bool $relations |
||||
97 | */ |
||||
98 | private function updateMappingHash($object, $relations) |
||||
99 | { |
||||
100 | $filter = [ |
||||
101 | 'MapperService' => $this->importerKey, |
||||
102 | 'ForRelations' => $relations, |
||||
103 | ]; |
||||
104 | /** @var MapperHash $mapperHash */ |
||||
105 | $mapperHash = $object->MapperHashes()->filter($filter)->first(); |
||||
106 | if ($mapperHash == null) { |
||||
107 | $mapperHash = MapperHash::create(); |
||||
108 | $mapperHash->MappedObjectID = $object->ID; |
||||
109 | $mapperHash->MappedObjectClass = $object->ClassName; |
||||
110 | $mapperHash->MapperService = $this->importerKey; |
||||
111 | $mapperHash->ForRelations = $relations; |
||||
112 | } |
||||
113 | |||||
114 | $mapperHash->MapperHash = $this->getMappingHash(); |
||||
115 | if ($mapperHash->isChanged() && $object->ID) { |
||||
116 | $mapperHash->write(); |
||||
117 | } |
||||
118 | } |
||||
119 | |||||
120 | /** |
||||
121 | * |
||||
122 | */ |
||||
123 | public function resetProductStream() |
||||
124 | { |
||||
125 | $this->productStream = JsonMachine::fromFile($this->file, '/4/products'); |
||||
126 | } |
||||
127 | |||||
128 | /** |
||||
129 | * |
||||
130 | */ |
||||
131 | public function resetAssetStream() |
||||
132 | { |
||||
133 | $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets'); |
||||
134 | } |
||||
135 | |||||
136 | /** |
||||
137 | * Maps the data |
||||
138 | * @throws \Exception |
||||
139 | */ |
||||
140 | public function map() |
||||
141 | { |
||||
142 | $this->extend('onBeforeMap', $this->file, Mapper::$MULTIPLE); |
||||
143 | |||||
144 | foreach ($this->yieldKeyVal($this->productStream) as $name => $data) { |
||||
145 | foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) { |
||||
146 | $this->mapToObject($class, $mappings, $data); |
||||
0 ignored issues
–
show
It seems like
$class can also be of type true ; however, parameter $class of Dynamic\Salsify\Model\Mapper::mapToObject() does only seem to accept SilverStripe\ORM\DataObject|string , 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
![]() |
|||||
147 | $this->currentUniqueFields = []; |
||||
148 | } |
||||
149 | } |
||||
150 | |||||
151 | if ($this->mappingHasSalsifyRelation()) { |
||||
152 | ImportTask::output("----------------"); |
||||
153 | ImportTask::output("Setting up salsify relations"); |
||||
154 | ImportTask::output("----------------"); |
||||
155 | $this->resetProductStream(); |
||||
156 | |||||
157 | foreach ($this->yieldKeyVal($this->productStream) as $name => $data) { |
||||
158 | foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) { |
||||
159 | $this->mapToObject($class, $mappings, $data, null, true); |
||||
160 | $this->currentUniqueFields = []; |
||||
161 | } |
||||
162 | } |
||||
163 | } |
||||
164 | |||||
165 | ImportTask::output("Imported and updated $this->importCount products."); |
||||
166 | $this->extend('onAfterMap', $this->file, Mapper::$MULTIPLE); |
||||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * @param string|DataObject $class |
||||
171 | * @param array $mappings The mapping for a specific class |
||||
172 | * @param array $data |
||||
173 | * @param DataObject|null $object |
||||
174 | * @param bool $salsifyRelations |
||||
175 | * @param bool $forceUpdate |
||||
176 | * |
||||
177 | * @return DataObject|null |
||||
178 | * @throws \Exception |
||||
179 | */ |
||||
180 | public function mapToObject( |
||||
181 | $class, |
||||
182 | $mappings, |
||||
183 | $data, |
||||
184 | $object = null, |
||||
185 | $salsifyRelations = false, |
||||
186 | $forceUpdate = false |
||||
187 | ) { |
||||
188 | if ($salsifyRelations) { |
||||
189 | if (!$this->classConfigHasSalsifyRelation($mappings)) { |
||||
190 | return null; |
||||
191 | } |
||||
192 | } |
||||
193 | |||||
194 | // if object was not passed |
||||
195 | if ($object === null) { |
||||
196 | $object = $this->findObjectByUnique($class, $mappings, $data); |
||||
197 | |||||
198 | // if no existing object was found but a unique filter is valid (not empty) |
||||
199 | if (!$object) { |
||||
0 ignored issues
–
show
|
|||||
200 | // don't try to create related objects that don't exist |
||||
201 | if ($salsifyRelations) { |
||||
202 | return null; |
||||
203 | } |
||||
204 | |||||
205 | if (!$this->hasValidUniqueFilter($class, $mappings, $data)) { |
||||
206 | return null; |
||||
207 | } |
||||
208 | |||||
209 | $object = $class::create(); |
||||
210 | } |
||||
211 | } |
||||
212 | |||||
213 | $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false; |
||||
214 | $wasWritten = $object->isInDB(); |
||||
215 | |||||
216 | $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0]; |
||||
217 | if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) { |
||||
218 | $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']]; |
||||
219 | } else { |
||||
220 | $firstUniqueValue = 'NULL'; |
||||
221 | } |
||||
222 | |||||
223 | if ($this->hasMethod('shouldSkipForObject')) { |
||||
224 | if ($this->shouldSkipForObject($object, $data)) { |
||||
0 ignored issues
–
show
The method
shouldSkipForObject() does not exist on Dynamic\Salsify\Model\Mapper . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
225 | ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue. Not the right object class"); |
||||
226 | return; |
||||
227 | } |
||||
228 | } |
||||
229 | |||||
230 | ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue"); |
||||
231 | |||||
232 | if ( |
||||
233 | !$forceUpdate && |
||||
234 | $this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations) |
||||
235 | ) { |
||||
236 | return $object; |
||||
237 | } |
||||
238 | |||||
239 | if (array_key_exists('salsify:parent_id', $data) && $this->hasFile()) { |
||||
240 | $products = JsonMachine::fromFile($this->file, '/4/products'); |
||||
241 | foreach ($this->yieldSingle($products) as $product) { |
||||
242 | if ($product['salsify:id'] === $data['salsify:parent_id']) { |
||||
243 | $data = array_merge($product, $data); |
||||
244 | break; |
||||
245 | } |
||||
246 | } |
||||
247 | } |
||||
248 | |||||
249 | foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) { |
||||
250 | $field = $this->getField($salsifyField, $data); |
||||
251 | if ($field === false) { |
||||
252 | $this->clearValue($object, $dbField, $salsifyField); |
||||
253 | continue; |
||||
254 | } |
||||
255 | |||||
256 | $type = $this->getFieldType($salsifyField); |
||||
257 | // skip all but salsify relations types if not doing relations |
||||
258 | if ($salsifyRelations && !$this->typeRequiresSalsifyObjects($type)) { |
||||
259 | continue; |
||||
260 | } |
||||
261 | |||||
262 | // skip salsify relations types if not doing relations |
||||
263 | if (!$salsifyRelations && $this->typeRequiresSalsifyObjects($type)) { |
||||
264 | continue; |
||||
265 | } |
||||
266 | |||||
267 | $value = null; |
||||
268 | $objectData = $data; |
||||
0 ignored issues
–
show
|
|||||
269 | |||||
270 | if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) { |
||||
0 ignored issues
–
show
It seems like
$dbField can also be of type true ; however, parameter $dbField of Dynamic\Salsify\Model\Mapper::handleShouldSkip() does only seem to accept string , 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
![]() |
|||||
271 | if (!$this->skipSilently) { |
||||
272 | ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue"); |
||||
273 | } |
||||
274 | return false; |
||||
0 ignored issues
–
show
|
|||||
275 | }; |
||||
276 | |||||
277 | $objectData = $this->handleModification($type, $class, $dbField, $salsifyField, $data); |
||||
0 ignored issues
–
show
It seems like
$dbField can also be of type true ; however, parameter $dbField of Dynamic\Salsify\Model\Mapper::handleModification() does only seem to accept string , 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
![]() |
|||||
278 | $sortColumn = $this->getSortColumn($salsifyField); |
||||
279 | |||||
280 | if ($salsifyRelations == false && !array_key_exists($field, $objectData)) { |
||||
0 ignored issues
–
show
|
|||||
281 | continue; |
||||
282 | } |
||||
283 | |||||
284 | $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField); |
||||
0 ignored issues
–
show
It seems like
$dbField can also be of type true ; however, parameter $dbField of Dynamic\Salsify\Model\Mapper::handleType() does only seem to accept string , 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
![]() |
|||||
285 | $this->writeValue($object, $type, $dbField, $value, $sortColumn); |
||||
0 ignored issues
–
show
It seems like
$dbField can also be of type true ; however, parameter $dbField of Dynamic\Salsify\Model\Mapper::writeValue() does only seem to accept string , 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
![]() |
|||||
286 | } |
||||
287 | |||||
288 | $this->extend('beforeObjectWrite', $object); |
||||
289 | if ($object->isChanged()) { |
||||
290 | $object->write(); |
||||
291 | $this->importCount++; |
||||
292 | $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished); |
||||
293 | } else { |
||||
294 | ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed."); |
||||
295 | } |
||||
296 | |||||
297 | if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) { |
||||
298 | $this->updateMappingHash($object, $salsifyRelations); |
||||
299 | } |
||||
300 | |||||
301 | return $object; |
||||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * @param DataObject $object |
||||
306 | * @param array $data |
||||
307 | * @param string $firstUniqueKey |
||||
308 | * @param string $firstUniqueValue |
||||
309 | * @param bool $salsifyRelations |
||||
310 | * @return bool |
||||
311 | */ |
||||
312 | private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false) |
||||
313 | { |
||||
314 | if ($this->config()->get('skipUpToDate') == false) { |
||||
315 | return false; |
||||
316 | } |
||||
317 | |||||
318 | if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) { |
||||
319 | ImportTask::output("Forcing update for $object->ClassName $firstUniqueKey $firstUniqueValue. Mappings changed."); |
||||
320 | return false; |
||||
321 | } |
||||
322 | |||||
323 | if ($salsifyRelations == false) { |
||||
0 ignored issues
–
show
|
|||||
324 | if ($this->objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) { |
||||
325 | ImportTask::output("Skipping $object->ClassName $firstUniqueKey $firstUniqueValue. It is up to Date."); |
||||
326 | return true; |
||||
327 | } |
||||
328 | } else { |
||||
329 | if ($this->objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) { |
||||
330 | ImportTask::output("Skipping $object->ClassName $firstUniqueKey $firstUniqueValue relations. It is up to Date."); |
||||
331 | return true; |
||||
332 | } |
||||
333 | } |
||||
334 | |||||
335 | return false; |
||||
336 | } |
||||
337 | |||||
338 | /** |
||||
339 | * @param DataObject $object |
||||
340 | * @param array $data |
||||
341 | * @param string $firstUniqueKey |
||||
342 | * @param string $firstUniqueValue |
||||
343 | * @return bool |
||||
344 | */ |
||||
345 | public function objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue) |
||||
346 | { |
||||
347 | // assume not up to date if field does not exist on object |
||||
348 | if (!$object->hasField('SalsifyUpdatedAt')) { |
||||
349 | return false; |
||||
350 | } |
||||
351 | |||||
352 | if ($data['salsify:updated_at'] != $object->getField('SalsifyUpdatedAt')) { |
||||
353 | return false; |
||||
354 | } |
||||
355 | |||||
356 | return true; |
||||
357 | } |
||||
358 | |||||
359 | /** |
||||
360 | * @param DataObject $object |
||||
361 | * @param array $data |
||||
362 | * @param string $firstUniqueKey |
||||
363 | * @param string $firstUniqueValue |
||||
364 | * @return bool |
||||
365 | */ |
||||
366 | public function objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue) |
||||
367 | { |
||||
368 | // assume not up to date if field does not exist on object |
||||
369 | if (!$object->hasField('SalsifyRelationsUpdatedAt')) { |
||||
370 | return false; |
||||
371 | } |
||||
372 | |||||
373 | // relations were never updated, so its up to date |
||||
374 | if (!isset($data['salsify:relations_updated_at'])) { |
||||
375 | return true; |
||||
376 | } |
||||
377 | |||||
378 | if ($data['salsify:relations_updated_at'] != $object->getField('SalsifyRelationsUpdatedAt')) { |
||||
379 | return false; |
||||
380 | } |
||||
381 | |||||
382 | return true; |
||||
383 | } |
||||
384 | |||||
385 | /** |
||||
386 | * @param array $salsifyField |
||||
387 | * @param array $data |
||||
388 | * |
||||
389 | * @return string|false |
||||
390 | */ |
||||
391 | private function getField($salsifyField, $data) |
||||
392 | { |
||||
393 | if (!is_array($salsifyField)) { |
||||
0 ignored issues
–
show
|
|||||
394 | return array_key_exists($salsifyField, $data) ? $salsifyField : false; |
||||
395 | } |
||||
396 | |||||
397 | $hasSalsifyField = array_key_exists('salsifyField', $salsifyField); |
||||
398 | $isLiteralField = ( |
||||
399 | $this->getFieldType($salsifyField) === 'Literal' && |
||||
0 ignored issues
–
show
|
|||||
400 | array_key_exists('value', $salsifyField) |
||||
401 | ); |
||||
402 | $isSalsifyRelationField = ( |
||||
403 | $this->getFieldType($salsifyField) === 'SalsifyRelation' && |
||||
0 ignored issues
–
show
|
|||||
404 | $hasSalsifyField |
||||
405 | ); |
||||
406 | |||||
407 | if ($isLiteralField) { |
||||
0 ignored issues
–
show
|
|||||
408 | return $salsifyField['value']; |
||||
409 | } |
||||
410 | |||||
411 | if ($isSalsifyRelationField) { |
||||
0 ignored issues
–
show
|
|||||
412 | return $salsifyField['salsifyField']; |
||||
413 | } |
||||
414 | |||||
415 | if (!$hasSalsifyField) { |
||||
416 | return false; |
||||
417 | } |
||||
418 | |||||
419 | if (array_key_exists($salsifyField['salsifyField'], $data)) { |
||||
420 | return $salsifyField['salsifyField']; |
||||
421 | } elseif (array_key_exists('fallback', $salsifyField)) { |
||||
422 | // make fallback an array |
||||
423 | if (!is_array($salsifyField['fallback'])) { |
||||
424 | $salsifyField['fallback'] = [$salsifyField['fallback']]; |
||||
425 | } |
||||
426 | |||||
427 | foreach ($this->yieldSingle($salsifyField['fallback']) as $fallback) { |
||||
428 | if (array_key_exists($fallback, $data)) { |
||||
429 | return $fallback; |
||||
430 | } |
||||
431 | } |
||||
432 | } elseif (array_key_exists('modification', $salsifyField)) { |
||||
433 | return $salsifyField['salsifyField']; |
||||
434 | } |
||||
435 | |||||
436 | return false; |
||||
437 | } |
||||
438 | |||||
439 | /** |
||||
440 | * @param string $class |
||||
441 | * @param array $mappings |
||||
442 | * @param array $data |
||||
443 | * |
||||
444 | * @return bool |
||||
445 | */ |
||||
446 | private function hasValidUniqueFilter($class, $mappings, $data) |
||||
447 | { |
||||
448 | return (bool) count(array_filter($this->getUniqueFilter($class, $mappings, $data))); |
||||
449 | } |
||||
450 | |||||
451 | /** |
||||
452 | * @param string $class |
||||
453 | * @param array $mappings |
||||
454 | * @param array $data |
||||
455 | * |
||||
456 | * @return array |
||||
457 | */ |
||||
458 | private function getUniqueFilter($class, $mappings, $data) |
||||
459 | { |
||||
460 | $uniqueFields = $this->uniqueFields($class, $mappings); |
||||
461 | // creates a filter |
||||
462 | $filter = []; |
||||
463 | foreach ($this->yieldKeyVal($uniqueFields) as $dbField => $salsifyField) { |
||||
464 | $modifiedData = $data; |
||||
465 | $fieldMapping = $mappings[$dbField]; |
||||
466 | $fieldType = $this->getFieldType($salsifyField); |
||||
467 | $modifiedData = $this->handleModification($fieldType, $class, $dbField, $fieldMapping, $modifiedData); |
||||
0 ignored issues
–
show
It seems like
$dbField can also be of type true ; however, parameter $dbField of Dynamic\Salsify\Model\Mapper::handleModification() does only seem to accept string , 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
![]() |
|||||
468 | |||||
469 | // adds unique fields to filter |
||||
470 | if (array_key_exists($salsifyField, $modifiedData)) { |
||||
471 | $filter[$dbField] = $modifiedData[$salsifyField]; |
||||
472 | } |
||||
473 | } |
||||
474 | |||||
475 | return $filter; |
||||
476 | } |
||||
477 | |||||
478 | /** |
||||
479 | * @param string $class |
||||
480 | * @param array $mappings |
||||
481 | * @param array $data |
||||
482 | * |
||||
483 | * @return \SilverStripe\ORM\DataObject |
||||
484 | */ |
||||
485 | private function findObjectByUnique($class, $mappings, $data) |
||||
486 | { |
||||
487 | if ($obj = $this->findBySalsifyID($class, $mappings, $data)) { |
||||
488 | return $obj; |
||||
489 | } |
||||
490 | |||||
491 | $filter = $this->getUniqueFilter($class, $mappings, $data); |
||||
492 | return DataObject::get($class)->filter($filter)->first(); |
||||
493 | } |
||||
494 | |||||
495 | /** |
||||
496 | * @param string $class |
||||
497 | * @param array $mappings |
||||
498 | * @param array $data |
||||
499 | * |
||||
500 | * @return \SilverStripe\ORM\DataObject|bool |
||||
501 | */ |
||||
502 | private function findBySalsifyID($class, $mappings, $data) |
||||
503 | { |
||||
504 | /** @var DataObject $genericObject */ |
||||
505 | $genericObject = Injector::inst()->get($class); |
||||
506 | if ( |
||||
507 | !$genericObject->hasExtension(SalsifyIDExtension::class) && |
||||
508 | !$genericObject->hasField('SalsifyID') |
||||
509 | ) { |
||||
510 | return false; |
||||
511 | } |
||||
512 | |||||
513 | $obj = DataObject::get($class)->filter([ |
||||
514 | 'SalsifyID' => $data['salsify:id'], |
||||
515 | ])->first(); |
||||
516 | if ($obj) { |
||||
517 | return $obj; |
||||
518 | } |
||||
519 | |||||
520 | return false; |
||||
521 | } |
||||
522 | |||||
523 | /** |
||||
524 | * Gets a list of all the unique field keys |
||||
525 | * |
||||
526 | * @param string class |
||||
0 ignored issues
–
show
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
527 | * @param array $mappings |
||||
528 | * @return array |
||||
529 | */ |
||||
530 | private function uniqueFields($class, $mappings) |
||||
531 | { |
||||
532 | // cached after first map |
||||
533 | if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) { |
||||
534 | return $this->currentUniqueFields[$class]; |
||||
535 | } |
||||
536 | |||||
537 | $uniqueFields = []; |
||||
538 | foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) { |
||||
539 | if (!is_array($salsifyField)) { |
||||
540 | continue; |
||||
541 | } |
||||
542 | |||||
543 | if ( |
||||
544 | !array_key_exists('unique', $salsifyField) || |
||||
545 | !array_key_exists('salsifyField', $salsifyField) |
||||
546 | ) { |
||||
547 | continue; |
||||
548 | } |
||||
549 | |||||
550 | if ($salsifyField['unique'] !== true) { |
||||
551 | continue; |
||||
552 | } |
||||
553 | |||||
554 | $uniqueFields[$dbField] = $salsifyField['salsifyField']; |
||||
555 | } |
||||
556 | |||||
557 | $this->currentUniqueFields[$class] = $uniqueFields; |
||||
558 | return $uniqueFields; |
||||
559 | } |
||||
560 | |||||
561 | /** |
||||
562 | * @param array|string $salsifyField |
||||
563 | * @return bool|mixed |
||||
564 | */ |
||||
565 | private function getSortColumn($salsifyField) |
||||
566 | { |
||||
567 | if (!is_array($salsifyField)) { |
||||
568 | return false; |
||||
569 | } |
||||
570 | |||||
571 | if (array_key_exists('sortColumn', $salsifyField)) { |
||||
572 | return $salsifyField['sortColumn']; |
||||
573 | } |
||||
574 | |||||
575 | return false; |
||||
576 | } |
||||
577 | |||||
578 | /** |
||||
579 | * @return bool |
||||
580 | */ |
||||
581 | private function mappingHasSalsifyRelation() |
||||
582 | { |
||||
583 | foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) { |
||||
584 | if ($this->classConfigHasSalsifyRelation($mappings)) { |
||||
585 | return true; |
||||
586 | } |
||||
587 | } |
||||
588 | return false; |
||||
589 | } |
||||
590 | |||||
591 | /** |
||||
592 | * @param array $classConfig |
||||
593 | * @return bool |
||||
594 | */ |
||||
595 | private function classConfigHasSalsifyRelation($classConfig) |
||||
596 | { |
||||
597 | foreach ($this->yieldKeyVal($classConfig) as $field => $config) { |
||||
598 | if (!is_array($config)) { |
||||
599 | continue; |
||||
600 | } |
||||
601 | |||||
602 | if (!array_key_exists('salsifyField', $config)) { |
||||
603 | continue; |
||||
604 | } |
||||
605 | |||||
606 | if (!array_key_exists('type', $config)) { |
||||
607 | continue; |
||||
608 | } |
||||
609 | |||||
610 | if (in_array($config['type'], $this->getFieldsRequiringSalsifyObjects())) { |
||||
611 | return true; |
||||
612 | } |
||||
613 | } |
||||
614 | return false; |
||||
615 | } |
||||
616 | |||||
617 | /** |
||||
618 | * @return array |
||||
619 | */ |
||||
620 | private function getFieldsRequiringSalsifyObjects() |
||||
621 | { |
||||
622 | $fieldTypes = $this->config()->get('field_types'); |
||||
0 ignored issues
–
show
|
|||||
623 | $types = []; |
||||
624 | foreach ($this->yieldKeyVal($this->config()->get('field_types')) as $field => $config) { |
||||
625 | $type = [ |
||||
626 | 'type' => $field, |
||||
627 | 'config' => $config, |
||||
628 | ]; |
||||
629 | if ($this->typeRequiresSalsifyObjects($type)) { |
||||
630 | $types[] = $field; |
||||
631 | } |
||||
632 | } |
||||
633 | |||||
634 | return $types; |
||||
635 | } |
||||
636 | |||||
637 | /** |
||||
638 | * @param array $type |
||||
639 | * @return bool |
||||
640 | */ |
||||
641 | private function typeRequiresWrite($type) |
||||
642 | { |
||||
643 | $config = $type['config']; |
||||
644 | |||||
645 | if (array_key_exists('requiresWrite', $config)) { |
||||
646 | return $config['requiresWrite']; |
||||
647 | } |
||||
648 | |||||
649 | return false; |
||||
650 | } |
||||
651 | |||||
652 | /** |
||||
653 | * @param array $type |
||||
654 | * @return bool |
||||
655 | */ |
||||
656 | private function typeRequiresSalsifyObjects($type) |
||||
657 | { |
||||
658 | $config = $type['config']; |
||||
659 | |||||
660 | if (array_key_exists('requiresSalsifyObjects', $config)) { |
||||
661 | return $config['requiresSalsifyObjects']; |
||||
662 | } |
||||
663 | |||||
664 | return false; |
||||
665 | } |
||||
666 | |||||
667 | /** |
||||
668 | * @param array $type |
||||
669 | * @return string|bool |
||||
670 | */ |
||||
671 | private function typeFallback($type) |
||||
672 | { |
||||
673 | $config = $type['config']; |
||||
674 | |||||
675 | if (array_key_exists('fallback', $config)) { |
||||
676 | return $config['fallback']; |
||||
677 | } |
||||
678 | |||||
679 | return false; |
||||
680 | } |
||||
681 | |||||
682 | /** |
||||
683 | * @param array $type |
||||
684 | * @return bool |
||||
685 | */ |
||||
686 | private function canModifyType($type) |
||||
687 | { |
||||
688 | $config = $type['config']; |
||||
689 | |||||
690 | if (array_key_exists('allowsModification', $config)) { |
||||
691 | return $config['allowsModification']; |
||||
692 | } |
||||
693 | |||||
694 | return true; |
||||
695 | } |
||||
696 | |||||
697 | /** |
||||
698 | * @param array $type |
||||
699 | * @param string $class |
||||
700 | * @param string $dbField |
||||
701 | * @param array $config |
||||
702 | * @param array $data |
||||
703 | * @return array |
||||
704 | */ |
||||
705 | private function handleModification($type, $class, $dbField, $config, $data) |
||||
706 | { |
||||
707 | if (!is_array($config)) { |
||||
0 ignored issues
–
show
|
|||||
708 | return $data; |
||||
709 | } |
||||
710 | |||||
711 | if (!$this->canModifyType($type)) { |
||||
712 | return $data; |
||||
713 | } |
||||
714 | |||||
715 | if (array_key_exists('modification', $config)) { |
||||
716 | $mod = $config['modification']; |
||||
717 | if ($this->hasMethod($mod)) { |
||||
718 | return $this->{$mod}($class, $dbField, $config, $data); |
||||
719 | } |
||||
720 | ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}."); |
||||
721 | } |
||||
722 | return $data; |
||||
723 | } |
||||
724 | |||||
725 | /** |
||||
726 | * @param string $class |
||||
727 | * @param string $dbField |
||||
728 | * @param array $config |
||||
729 | * @param array $data |
||||
730 | * @return boolean |
||||
731 | */ |
||||
732 | public function handleShouldSkip($class, $dbField, $config, $data) |
||||
733 | { |
||||
734 | if (!is_array($config)) { |
||||
0 ignored issues
–
show
|
|||||
735 | return false; |
||||
736 | } |
||||
737 | |||||
738 | if (array_key_exists('shouldSkip', $config)) { |
||||
739 | $skipMethod = $config['shouldSkip']; |
||||
740 | if ($this->hasMethod($skipMethod)) { |
||||
741 | return $this->{$skipMethod}($class, $dbField, $config, $data); |
||||
742 | } |
||||
743 | ImportTask::output( |
||||
744 | "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}." |
||||
745 | ); |
||||
746 | } |
||||
747 | return false; |
||||
748 | } |
||||
749 | |||||
750 | /** |
||||
751 | * @param string|array $field |
||||
752 | * @return array |
||||
753 | */ |
||||
754 | public function getFieldType($field) |
||||
755 | { |
||||
756 | $fieldTypes = $this->config()->get('field_types'); |
||||
757 | if (is_array($field) && array_key_exists('type', $field)) { |
||||
758 | if (array_key_exists($field['type'], $fieldTypes)) { |
||||
759 | return [ |
||||
760 | 'type' => $field['type'], |
||||
761 | 'config' => $fieldTypes[$field['type']], |
||||
762 | ]; |
||||
763 | } |
||||
764 | } |
||||
765 | // default to raw |
||||
766 | return [ |
||||
767 | 'type' => 'Raw', |
||||
768 | 'config' => $fieldTypes['Raw'], |
||||
769 | ]; |
||||
770 | } |
||||
771 | |||||
772 | /** |
||||
773 | * @param array $type |
||||
774 | * @param string|DataObject $class |
||||
775 | * @param array $salsifyData |
||||
776 | * @param string $salsifyField |
||||
777 | * @param array $dbFieldConfig |
||||
778 | * @param string $dbField |
||||
779 | * |
||||
780 | * @return mixed |
||||
781 | */ |
||||
782 | private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField) |
||||
783 | { |
||||
784 | $typeName = $type['type']; |
||||
785 | $typeConfig = $type['config']; |
||||
786 | if ($this->hasMethod("handle{$typeName}Type")) { |
||||
787 | return $this->{"handle{$typeName}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField); |
||||
788 | } |
||||
789 | |||||
790 | if (array_key_exists('fallback', $typeConfig)) { |
||||
791 | $fallback = $typeConfig['fallback']; |
||||
792 | if ($this->hasMethod("handle{$fallback}Type")) { |
||||
793 | return $this->{"handle{$fallback}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField); |
||||
794 | } |
||||
795 | } |
||||
796 | |||||
797 | ImportTask::output("{$typeName} is not a valid type. skipping field {$dbField}."); |
||||
798 | return ''; |
||||
799 | } |
||||
800 | |||||
801 | /** |
||||
802 | * @param DataObject $object |
||||
803 | * @param array $type |
||||
804 | * @param string $dbField |
||||
805 | * @param mixed $value |
||||
806 | * @param string|bool $sortColumn |
||||
807 | * |
||||
808 | * @throws \Exception |
||||
809 | */ |
||||
810 | private function writeValue($object, $type, $dbField, $value, $sortColumn) |
||||
811 | { |
||||
812 | $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) || |
||||
813 | array_key_exists($dbField, $object->config()->get('many_many')) || |
||||
814 | array_key_exists($dbField, $object->config()->get('belongs_many_many')); |
||||
815 | |||||
816 | $isSingleRelation = array_key_exists(rtrim($dbField, 'ID'), $object->config()->get('has_one')); |
||||
817 | |||||
818 | // write the object so relations can be written |
||||
819 | if ($this->typeRequiresWrite($type) && !$object->exists()) { |
||||
820 | $this->extend('beforeObjectWrite', $object); |
||||
821 | $object->write(); |
||||
822 | } |
||||
823 | |||||
824 | if (!$isManyRelation) { |
||||
825 | if (!$isSingleRelation || ($isSingleRelation && $value !== false)) { |
||||
826 | $object->$dbField = $value; |
||||
827 | } |
||||
828 | return; |
||||
829 | } |
||||
830 | |||||
831 | // change to an array and filter out empty values |
||||
832 | if (!is_array($value)) { |
||||
833 | $value = [$value]; |
||||
834 | } |
||||
835 | $value = array_filter($value); |
||||
836 | |||||
837 | // don't try to write an empty set |
||||
838 | if (!count($value)) { |
||||
839 | return; |
||||
840 | } |
||||
841 | |||||
842 | $this->removeUnrelated($object, $dbField, $value); |
||||
843 | $this->writeManyRelation($object, $dbField, $value, $sortColumn); |
||||
844 | } |
||||
845 | |||||
846 | /** |
||||
847 | * @param DataObject $object |
||||
848 | * @param string $dbField |
||||
849 | * @param array $value |
||||
850 | * @param string|bool $sortColumn |
||||
851 | * |
||||
852 | * @throws \Exception |
||||
853 | */ |
||||
854 | private function writeManyRelation($object, $dbField, $value, $sortColumn) |
||||
855 | { |
||||
856 | /** @var DataList|HasManyList|ManyManyList $relation */ |
||||
857 | $relation = $object->{$dbField}(); |
||||
858 | |||||
859 | if ($sortColumn && $relation instanceof ManyManyList) { |
||||
860 | for ($i = 0; $i < count($value); $i++) { |
||||
0 ignored issues
–
show
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
}
![]() |
|||||
861 | $relation->add($value[$i], [$sortColumn => $i]); |
||||
862 | } |
||||
863 | return; |
||||
864 | } |
||||
865 | |||||
866 | // HasManyList, so it exists on the value |
||||
867 | if ($sortColumn) { |
||||
868 | for ($i = 0; $i < count($value); $i++) { |
||||
0 ignored issues
–
show
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
}
![]() |
|||||
869 | $value[$i]->{$sortColumn} = $i; |
||||
870 | $relation->add($value[$i]); |
||||
871 | } |
||||
872 | return; |
||||
873 | } |
||||
874 | |||||
875 | $relation->addMany($value); |
||||
876 | } |
||||
877 | |||||
878 | /** |
||||
879 | * Removes unrelated objects in the relation that were previously related |
||||
880 | * @param DataObject $object |
||||
881 | * @param string $dbField |
||||
882 | * @param array $value |
||||
883 | */ |
||||
884 | private function removeUnrelated($object, $dbField, $value) |
||||
885 | { |
||||
886 | $ids = []; |
||||
887 | foreach ($value as $v) { |
||||
888 | $ids[] = $v->ID; |
||||
889 | } |
||||
890 | |||||
891 | /** @var DataList $relation */ |
||||
892 | $relation = $object->{$dbField}(); |
||||
893 | // remove all unrelated - removeAll had an odd side effect (relations only got added back half the time) |
||||
894 | if (!empty($ids)) { |
||||
895 | $relation->removeMany( |
||||
896 | $relation->exclude([ |
||||
897 | 'ID' => $ids, |
||||
898 | ])->column('ID') |
||||
899 | ); |
||||
900 | } |
||||
901 | } |
||||
902 | |||||
903 | /** |
||||
904 | * @param string|array $salsifyField |
||||
905 | * @throws Exception |
||||
906 | */ |
||||
907 | private function clearValue($object, $dbField, $salsifyField) |
||||
908 | { |
||||
909 | if ( |
||||
910 | is_array($salsifyField) && |
||||
911 | array_key_exists('keepExistingValue', $salsifyField) && |
||||
912 | $salsifyField['keepExistingValue'] |
||||
913 | ) { |
||||
914 | return; |
||||
915 | } |
||||
916 | |||||
917 | $type = [ |
||||
918 | 'type' => 'null', |
||||
919 | 'config' => [], |
||||
920 | ]; |
||||
921 | |||||
922 | // clear any existing value |
||||
923 | $this->writeValue($object, $type, $dbField, null, null); |
||||
924 | } |
||||
925 | |||||
926 | /** |
||||
927 | * @return JsonMachine |
||||
928 | */ |
||||
929 | public function getProductStream() |
||||
930 | { |
||||
931 | return $this->productStream; |
||||
932 | } |
||||
933 | |||||
934 | /** |
||||
935 | * @return JsonMachine |
||||
936 | */ |
||||
937 | public function getAssetStream() |
||||
938 | { |
||||
939 | return $this->assetStream; |
||||
940 | } |
||||
941 | |||||
942 | /** |
||||
943 | * @return bool |
||||
944 | */ |
||||
945 | public function hasFile() |
||||
946 | { |
||||
947 | return $this->file !== null; |
||||
948 | } |
||||
949 | |||||
950 | /** |
||||
951 | * @param DataObject|SalsifyIDExtension $object |
||||
952 | * @param bool $relations |
||||
953 | * |
||||
954 | * @return bool |
||||
955 | */ |
||||
956 | private function isMapperHashUpToDate($object, $relations) |
||||
957 | { |
||||
958 | if (!$object->hasMethod('MapperHashes')) { |
||||
0 ignored issues
–
show
The method
hasMethod() does not exist on Dynamic\Salsify\ORM\SalsifyIDExtension .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
959 | return true; |
||||
960 | } |
||||
961 | |||||
962 | $filter = [ |
||||
963 | 'MapperService' => $this->importerKey, |
||||
964 | 'ForRelations' => $relations, |
||||
965 | ]; |
||||
966 | /** @var MapperHash $hash */ |
||||
967 | if ($hash = $object->MapperHashes()->filter($filter)->first()) { |
||||
968 | if ($hash->MapperHash != $this->getMappingHash()) { |
||||
969 | return false; |
||||
970 | } |
||||
971 | } else { |
||||
972 | return false; |
||||
973 | } |
||||
974 | |||||
975 | return true; |
||||
976 | } |
||||
977 | } |
||||
978 |
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths