Passed
Pull Request — master (#18)
by Matthew
02:12
created

Mapper::handleModification()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 4
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\Task\ImportTask;
6
use Exception;
7
use JsonMachine\JsonMachine;
8
use SilverStripe\ORM\DataObject;
9
10
/**
11
 * Class Mapper
12
 * @package Dynamic\Salsify\Model
13
 */
14
class Mapper extends Service
15
{
16
17
    /**
18
     * @var
19
     */
20
    private $file = null;
21
22
    /**
23
     * @var JsonMachine
24
     */
25
    private $productStream;
26
27
    /**
28
     * @var JsonMachine
29
     */
30
    private $assetStream;
31
32
    /**
33
     * @var array
34
     */
35
    private $currentUniqueFields;
36
37
    /**
38
     * @var int
39
     */
40
    private $importCount = 0;
41
42
    /**
43
     * Mapper constructor.
44
     * @param string $importerKey
45
     * @param $file
46
     * @throws \Exception
47
     */
48
    public function __construct($importerKey, $file = null)
49
    {
50
        parent::__construct($importerKey);
51
        if (!$this->config()->get('mapping')) {
52
            throw  new Exception('A Mapper needs a mapping');
53
        }
54
55
        if ($file !== null) {
56
            $this->file = $file;
57
            $this->productStream = JsonMachine::fromFile($file, '/4/products');
58
            $this->resetAssetStream();
59
        }
60
    }
61
62
    /**
63
     *
64
     */
65
    public function resetAssetStream()
66
    {
67
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
68
    }
69
70
    /**
71
     * Maps the data
72
     * @throws \Exception
73
     */
74
    public function map()
75
    {
76
        foreach ($this->productStream as $name => $data) {
77
            foreach ($this->config()->get('mapping') as $class => $mappings) {
78
                $this->mapToObject($class, $mappings, $data);
79
                $this->currentUniqueFields = [];
80
            }
81
        }
82
        ImportTask::output("Imported and updated $this->importCount products.");
83
    }
84
85
    /**
86
     * @param string|DataObject $class
87
     * @param array $mappings The mapping for a specific class
88
     * @param array $data
89
     *
90
     * @return DataObject
91
     * @throws \Exception
92
     */
93
    public function mapToObject($class, $mappings, $data)
94
    {
95
        $object = $this->findObjectByUnique($class, $mappings, $data);
96
        if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
97
            $object = $class::create();
98
        }
99
100
        $firstUniqueKey = array_keys($this->uniqueFields($mappings))[0];
101
        $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
102
        ImportTask::output("Updating $firstUniqueKey $firstUniqueValue");
103
104
        foreach ($mappings as $dbField => $salsifyField) {
105
            $field = $salsifyField;
106
            $value = null;
107
            // default to raw
108
            $type = $this->getFieldType($salsifyField);
109
            $objectData = $data;
110
111
            if (is_array($salsifyField)) {
112
                if (!array_key_exists('salsifyField', $salsifyField)) {
113
                    continue;
114
                }
115
                $field = $salsifyField['salsifyField'];
116
117
                if (array_key_exists('modification', $salsifyField)) {
118
                    $objectData = $this->handleModification(
119
                        $salsifyField['modification'],
120
                        $dbField,
121
                        $salsifyField,
122
                        $data
123
                    );
124
                }
125
            }
126
127
            if (!array_key_exists($field, $objectData)) {
128
                continue;
129
            }
130
131
            $value = $this->handleType($type, $objectData, $field, $salsifyField, $dbField, $class);
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

131
            $value = $this->handleType(/** @scrutinizer ignore-type */ $type, $objectData, $field, $salsifyField, $dbField, $class);
Loading history...
132
            $this->writeValue($object, $dbField, $value);
133
        }
134
135
        if ($object->isChanged()) {
136
            $object->write();
137
            $this->importCount++;
138
        } else {
139
            ImportTask::output("$firstUniqueKey $firstUniqueValue was not changed.");
140
        }
141
        return $object;
142
    }
143
144
    /**
145
     * @param string $class
146
     * @param array $mappings
147
     * @param array $data
148
     *
149
     * @return \SilverStripe\ORM\DataObject
150
     */
151
    private function findObjectByUnique($class, $mappings, $data)
152
    {
153
        $uniqueFields = $this->uniqueFields($mappings);
154
        // creates a filter
155
        $filter = [];
156
        foreach ($uniqueFields as $dbField => $salsifyField) {
157
            $modifiedData = $data;
158
            $fieldMapping = $mappings[$dbField];
159
160
            if (array_key_exists('modification', $fieldMapping)) {
161
                $modifiedData = $this->handleModification(
162
                    $fieldMapping['modification'],
163
                    $dbField,
164
                    $fieldMapping,
165
                    $modifiedData
166
                );
167
            }
168
169
            // adds unique fields to filter
170
            $filter[$dbField] = $modifiedData[$salsifyField];
171
        }
172
173
        return DataObject::get($class)->filter($filter)->first();
174
    }
175
176
    /**
177
     * Gets a list of all the unique field keys
178
     *
179
     * @param array $mappings
180
     * @return array
181
     */
182
    private function uniqueFields($mappings)
183
    {
184
        // cached after first map
185
        if (!empty($this->currentUniqueFields)) {
186
            return $this->currentUniqueFields;
187
        }
188
189
        $uniqueFields = [];
190
        foreach ($mappings as $dbField => $salsifyField) {
191
            if (!is_array($salsifyField)) {
192
                continue;
193
            }
194
195
            if (!array_key_exists('unique', $salsifyField) ||
196
                !array_key_exists('salsifyField', $salsifyField)) {
197
                continue;
198
            }
199
200
            if ($salsifyField['unique'] !== true) {
201
                continue;
202
            }
203
204
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
205
        }
206
207
        $this->currentUniqueFields = $uniqueFields;
208
        return $uniqueFields;
209
    }
210
211
    /**
212
     * @param string $mod
213
     * @param string $dbField
214
     * @param array $config
215
     * @param array $data
216
     * @return array
217
     */
218
    private function handleModification($mod, $dbField, $config, $data)
219
    {
220
        if ($this->hasMethod($mod)) {
221
            return $this->{$mod}($dbField, $config, $data);
222
        }
223
        ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
224
        return $data;
225
    }
226
227
    /**
228
     * @param string|array $field
229
     * @return string
230
     */
231
    public function getFieldType($field)
232
    {
233
        $fieldTypes = $this->config()->get('field_types');
234
        if (is_array($field) && array_key_exists('type', $field)) {
235
            if (in_array($field['type'], $fieldTypes)) {
236
                return $field['type'];
237
            }
238
        }
239
        return 'Raw';
240
    }
241
242
    /**
243
     * @param int $type
244
     * @param array $salsifyData
245
     * @param string $salsifyField
246
     * @param array $dbFieldConfig
247
     * @param string $dbField
248
     * @param string $class
249
     *
250
     * @return mixed
251
     */
252
    private function handleType($type, $salsifyData, $salsifyField, $dbFieldConfig, $dbField, $class)
253
    {
254
        if ($this->hasMethod("handle{$type}Type")) {
255
            return $this->{"handle{$type}Type"}($salsifyData, $salsifyField, $dbFieldConfig, $dbField, $class);
256
        }
257
        ImportTask::output("{$type} is not a valid type. skipping field {$dbField}.");
258
        return '';
259
    }
260
261
    /**
262
     * @param DataObject $object
263
     * @param string $dbField
264
     * @param mixed $value
265
     *
266
     * @throws \Exception
267
     */
268
    private function writeValue($object, $dbField, $value)
269
    {
270
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
271
            array_key_exists($dbField, $object->config()->get('many_many')) ||
272
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
273
274
        if (!$isManyRelation) {
275
            $object->$dbField = $value;
276
            return;
277
        }
278
279
        if (!$object->exists()) {
280
            $object->write();
281
        }
282
283
        if (is_array($value)) {
284
            $object->{$dbField}()->addMany($value);
285
            return;
286
        }
287
288
        $object->{$dbField}()->add($value);
289
    }
290
291
    /**
292
     * @return \JsonMachine\JsonMachine
293
     */
294
    public function getAssetStream()
295
    {
296
        return $this->assetStream;
297
    }
298
299
    /**
300
     * @return bool
301
     */
302
    public function hasFile()
303
    {
304
        return $this->file !== null;
305
    }
306
}
307