Passed
Pull Request — master (#18)
by Matthew
02:00
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($salsifyField['modification'], $dbField, $salsifyField, $data);
119
                }
120
            }
121
122
            if (!array_key_exists($field, $objectData)) {
123
                continue;
124
            }
125
126
            $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

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