getValueCsvSerializerFactory()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * TechDivision\Import\Serializer\Csv\AdditionalAttributeCsvSerializer
5
 *
6
 * PHP version 7
7
 *
8
 * @author    Tim Wagner <[email protected]>
9
 * @copyright 2021 TechDivision GmbH <[email protected]>
10
 * @license   https://opensource.org/licenses/MIT
11
 * @link      https://github.com/techdivision/import-serializer-csv
12
 * @link      http://www.techdivision.com
13
 */
14
15
namespace TechDivision\Import\Serializer\Csv;
16
17
use TechDivision\Import\Serializer\SerializerInterface;
18
use TechDivision\Import\Serializer\SerializerFactoryInterface;
19
use TechDivision\Import\Serializer\Configuration\ConfigurationInterface;
20
use TechDivision\Import\Serializer\AdditionalAttributeSerializerInterface;
21
use TechDivision\Import\Serializer\Configuration\SerializerConfigurationInterface;
22
use TechDivision\Import\Serializer\Csv\Utils\MemberNames;
23
use TechDivision\Import\Serializer\Csv\Utils\FrontendInputTypes;
24
use TechDivision\Import\Serializer\Csv\Services\EavAttributeAwareProcessorInterface;
25
26
/**
27
 * Serializer implementation that un-/serializes the additional product attribues found in the CSV file
28
 * in the row 'additional_attributes'.
29
 *
30
 * @author    Tim Wagner <[email protected]>
31
 * @copyright 2021 TechDivision GmbH <[email protected]>
32
 * @license   https://opensource.org/licenses/MIT
33
 * @link      https://github.com/techdivision/import-serializer-csv
34
 * @link      http://www.techdivision.com
35
 */
36
class AdditionalAttributeCsvSerializer extends AbstractCsvSerializer implements AdditionalAttributeSerializerInterface
37
{
38
39
    /**
40
     * The factory instance for the CSV value serializer.
41
     *
42
     * @var \TechDivision\Import\Serializer\SerializerFactoryInterface
43
     */
44
    private $valueCsvSerializerFactory;
45
46
    /**
47
     * The CSV value serializer instance.
48
     *
49
     * @var \TechDivision\Import\Serializer\SerializerInterface
50
     */
51
    private $valueCsvSerializer;
52
53
    /**
54
     * The entity type from the configuration.
55
     *
56
     * @var array
57
     */
58
    private $entityType = array();
59
60
    /**
61
     *  The configuration instance.
62
     *
63
     * @var \TechDivision\Import\Serializer\Configuration\ConfigurationInterface
64
     */
65
    private $configuration;
66
67
    /**
68
     * The attribute loader instance.
69
     *
70
     * @var \TechDivision\Import\Serializer\Csv\Services\EavAttributeAwareProcessorInterface
71
     */
72
    private $eavAttributeAwareProcessor;
73
74
    /**
75
     * Initialize the serializer with the passed CSV value serializer factory.
76
     *
77
     * @param \TechDivision\Import\Serializer\Configuration\ConfigurationInterface             $configuration             The configuration instance
78
     * @param \TechDivision\Import\Serializer\Csv\Services\EavAttributeAwareProcessorInterface $attributeLoader           The attribute loader instance
79
     * @param \TechDivision\Import\Serializer\SerializerFactoryInterface                       $valueCsvSerializerFactory The CSV value serializer factory
80
     */
81
    public function __construct(
82
        ConfigurationInterface $configuration,
83
        EavAttributeAwareProcessorInterface $attributeLoader,
84
        SerializerFactoryInterface $valueCsvSerializerFactory
85
    ) {
86
87
        // set the passed instances
88
        $this->configuration = $configuration;
89
        $this->eavAttributeAwareProcessor = $attributeLoader;
90
        $this->valueCsvSerializerFactory = $valueCsvSerializerFactory;
91
92
        // load the entity type for the entity type defined in the configuration
93
        $this->entityType = $attributeLoader->getEavEntityTypeByEntityTypeCode($configuration->getEntityTypeCode());
94
95
        // pre-initialize the serialize with the values
96
        // found in the main configuration
97
        $this->init($configuration);
98
    }
99
100
    /**
101
     * Returns the configuration instance.
102
     *
103
     * @return \TechDivision\Import\Serializer\Configuration\SerializerConfigurationInterface The configuration instance
104
     */
105
    protected function getConfiguration()
106
    {
107
        return $this->configuration;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->configuration returns the type TechDivision\Import\Seri...\ConfigurationInterface which is incompatible with the documented return type TechDivision\Import\Seri...rConfigurationInterface.
Loading history...
108
    }
109
110
    /**
111
     * Returns the factory instance for the CSV value serializer.
112
     *
113
     * @return \TechDivision\Import\Serializer\SerializerFactoryInterface The CSV value serializer factory instance
114
     */
115
    protected function getValueCsvSerializerFactory()
116
    {
117
        return $this->valueCsvSerializerFactory;
118
    }
119
120
    /**
121
     * Returns the CSV value serializer instance.
122
     *
123
     * @param \TechDivision\Import\Serializer\SerializerInterface $valueCsvSerializer The CSV value serializer instance
124
     *
125
     * @return void
126
     */
127
    protected function setValueCsvSerializer(SerializerInterface $valueCsvSerializer)
128
    {
129
        $this->valueCsvSerializer = $valueCsvSerializer;
130
    }
131
132
    /**
133
     * Returns the CSV value serializer instance.
134
     *
135
     * @return \TechDivision\Import\Serializer\SerializerInterface The CSV value serializer instance
136
     */
137
    protected function getValueCsvSerializer()
138
    {
139
        return $this->valueCsvSerializer;
140
    }
141
142
    /**
143
     * Returns the EAV attribute aware processor instance.
144
     *
145
     * @return \TechDivision\Import\Serializer\Csv\Services\EavAttributeAwareProcessorInterface The EAV attribute aware processor instance
146
     */
147
    protected function getEavAttributeAwareProcessor() : EavAttributeAwareProcessorInterface
148
    {
149
        return $this->eavAttributeAwareProcessor;
150
    }
151
152
    /**
153
     * Returns entity type ID mapped from the configuration.
154
     *
155
     * @return integer The mapped entity type ID
156
     */
157
    protected function getEntityTypeId()
158
    {
159
        return $this->entityType[MemberNames::ENTITY_TYPE_ID];
160
    }
161
162
    /**
163
     * Returns the multiple value delimiter from the configuration.
164
     *
165
     * @return string The multiple value delimiter
166
     */
167
    protected function getMultipleValueDelimiter()
168
    {
169
        return $this->getConfiguration()->getMultipleValueDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleValueDelimiter() does not exist on TechDivision\Import\Seri...rConfigurationInterface. It seems like you code against a sub-type of TechDivision\Import\Seri...rConfigurationInterface such as TechDivision\Import\Seri...\ConfigurationInterface. ( Ignorable by Annotation )

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

169
        return $this->getConfiguration()->/** @scrutinizer ignore-call */ getMultipleValueDelimiter();
Loading history...
170
    }
171
172
    /**
173
     * Returns the multiple field delimiter from the configuration.
174
     *
175
     * @return string The multiple field delimiter
176
     */
177
    protected function getMultipleFieldDelimiter()
178
    {
179
        return $this->getConfiguration()->getMultipleFieldDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleFieldDelimiter() does not exist on TechDivision\Import\Seri...rConfigurationInterface. It seems like you code against a sub-type of TechDivision\Import\Seri...rConfigurationInterface such as TechDivision\Import\Seri...\ConfigurationInterface. ( Ignorable by Annotation )

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

179
        return $this->getConfiguration()->/** @scrutinizer ignore-call */ getMultipleFieldDelimiter();
Loading history...
180
    }
181
182
    /**
183
     * Loads and returns the attribute with the passed code from the database.
184
     *
185
     * @param string $attributeCode The code of the attribute to return
186
     *
187
     * @return array The attribute
188
     */
189
    protected function loadAttributeByAttributeCode($attributeCode)
190
    {
191
        return $this->getEavAttributeAwareProcessor()->getEavAttributeByEntityTypeIdAndAttributeCode($this->getEntityTypeId(), $attributeCode);
192
    }
193
194
    /**
195
     * Packs the passed value according to the frontend input type of the attribute with the passed code.
196
     *
197
     * @param string $attributeCode The code of the attribute to pack the passed value for
198
     * @param mixed  $value         The value to pack
199
     *
200
     * @return string The packed value
201
     */
202
    protected function pack($attributeCode, $value)
203
    {
204
205
        // load the attibute with the passed code
206
        $attribute = $this->loadAttributeByAttributeCode($attributeCode);
207
208
        // pack the value according to the attribute's frontend input type
209
        switch ($attribute[MemberNames::FRONTEND_INPUT]) {
210
            case FrontendInputTypes::MULTISELECT:
211
                return $this->implode($value, $this->getMultipleValueDelimiter());
212
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
213
214
            case FrontendInputTypes::BOOLEAN:
215
                return $value === true ? 'true' : 'false';
216
                break;
217
218
            default:
219
                return $value;
220
        }
221
    }
222
223
    /**
224
     * Unpacks the passed value according to the frontend input type of the attribute with the passed code.
225
     *
226
     * @param string $attributeCode The code of the attribute to pack the passed value for
227
     * @param string $value         The value to unpack
228
     *
229
     * @return mixed The unpacked value
230
     */
231
    protected function unpack($attributeCode, $value)
232
    {
233
234
        // load the attibute with the passed code
235
        $attribute = $this->loadAttributeByAttributeCode($attributeCode);
236
237
        // unpack the value according to the attribute's frontend input type
238
        switch ($attribute[MemberNames::FRONTEND_INPUT]) {
239
            case FrontendInputTypes::MULTISELECT:
240
                return $this->explode($value, $this->getMultipleValueDelimiter());
241
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
242
243
            case FrontendInputTypes::BOOLEAN:
244
                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
245
                break;
246
247
            default:
248
                return $value;
249
        }
250
    }
251
252
    /**
253
     * Passes the configuration and initializes the serializer.
254
     *
255
     * @param \TechDivision\Import\Serializer\Configuration\SerializerConfigurationInterface $configuration The CSV configuration
256
     *
257
     * @return void
258
     */
259
    public function init(SerializerConfigurationInterface $configuration)
260
    {
261
262
        // pass the configuration to the parent instance
263
        parent::init($configuration);
264
265
        // create the CSV value serializer instance
266
        $this->setValueCsvSerializer($this->getValueCsvSerializerFactory()->createSerializer($configuration));
267
    }
268
269
    /**
270
     * Unserializes the elements of the passed string.
271
     *
272
     * @param string|null $serialized The value to unserialize
273
     * @param string|null $delimiter  The delimiter used to unserialize the elements
274
     *
275
     * @return array The unserialized values
276
     * @see \TechDivision\Import\Serializer\SerializerInterface::unserialize()
277
     */
278
    public function unserialize($serialized = null, $delimiter = null)
279
    {
280
        return $this->getValueCsvSerializer()->explode($serialized, $delimiter ? $delimiter : $this->getMultipleFieldDelimiter());
281
    }
282
283
    /**
284
     * Serializes the elements of the passed array.
285
     *
286
     * @param array|null  $unserialized The serialized data
287
     * @param string|null $delimiter    The delimiter used to serialize the values
288
     *
289
     * @return string The serialized array
290
     * @see \TechDivision\Import\Serializer\SerializerInterface::serialize()
291
     */
292
    public function serialize(array $unserialized = null, $delimiter = null)
293
    {
294
        return $this->getValueCsvSerializer()->implode($unserialized, $delimiter ? $delimiter : $this->getMultipleFieldDelimiter());
295
    }
296
297
    /**
298
     * Extracts the elements of the passed value by exploding them
299
     * with the also passed delimiter.
300
     *
301
     * @param string|null $value     The value to extract
302
     * @param string|null $delimiter The delimiter used to extrace the elements
303
     *
304
     * @return array|null The exploded values
305
     * @see \TechDivision\Import\Serializer\SerializerInterface::unserialize()
306
     */
307
    public function explode($value = null, $delimiter = null)
308
    {
309
        return $this->unserialize($value, $delimiter);
310
    }
311
312
    /**
313
     * Compacts the elements of the passed value by imploding them
314
     * with the also passed delimiter.
315
     *
316
     * @param array|null  $value     The values to compact
317
     * @param string|null $delimiter The delimiter use to implode the values
318
     *
319
     * @return string|null The compatected value
320
     * @see \TechDivision\Import\Serializer\SerializerInterface::serialize()
321
     */
322
    public function implode(array $value = null, $delimiter = null)
323
    {
324
        return $this->serialize($value, $delimiter);
325
    }
326
327
    /**
328
     * Create a CSV compatible string from the passed category path.
329
     *
330
     * @param string|null $value  The normalized category path (usually from the DB)
331
     * @param bool        $unpack TRUE if the option values has to be unpacked, in case of a multiselect attribute
332
     *
333
     * @return array The array with the denormalized attribute values
334
     */
335
    public function denormalize(string $value = null, bool $unpack = true) : array
336
    {
337
338
        // initialize the array for the attributes
339
        $attrs = array();
340
341
        // explode the additional attributes and iterate
342
        // over the attributes and append them to the row
343
        if (is_array($additionalAttributes = $this->unserialize($value))) {
0 ignored issues
show
introduced by
The condition is_array($additionalAttr...s->unserialize($value)) is always true.
Loading history...
344
            foreach ($additionalAttributes as $additionalAttribute) {
345
                // initialize the option value
346
                $optionValue = '';
347
                // explode attribute code/option value from the attribute
348
                $exploded = $this->explode($additionalAttribute, '=');
349
                // initialize attribute code and option value, depending on what we've exploded
350
                if (!is_array($exploded) || sizeof($exploded) < 1) {
351
                    continue;
352
                } elseif (sizeof($exploded) === 1) {
353
                    list ($attributeCode) = $exploded;
354
                } else {
355
                    list ($attributeCode, $optionValue) = $exploded;
356
                }
357
                // query whether or not we've to pack the option
358
                // values in case we've a multiselect input field
359
                $optionValue = $unpack ? $this->unpack($attributeCode, $optionValue) : $optionValue;
360
                // unpack the attribute values and add them to the array
361
                $attrs[$attributeCode] = $optionValue;
362
            }
363
        }
364
365
        // return the extracted array with the additional attributes
366
        return $attrs;
367
    }
368
369
    /**
370
     * Normalizes the category path in a standard representation.
371
     *
372
     * For example this means, that a category  path `Default Category/Gear`
373
     * will be normalized into `"Default Category"/Gear`.
374
     *
375
     * @param array $values The category path that has to be normalized
376
     * @param bool  $pack   TRUE if the option values has to be packed, in case of a multiselect attribute
377
     *
378
     * @return string The normalized category path
379
     */
380
    public function normalize(array $values, bool $pack = true) : string
381
    {
382
383
        // initialize the array for the attributes
384
        $attrs = array();
385
386
        // iterate over the attributes and append them to the row
387
        if (is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
388
            foreach ($values as $attributeCode => $optionValue) {
389
                // query whether or not we've to unpack the option
390
                // values in case we've a multiselect input field
391
                $optionValue = $pack ? $this->pack($attributeCode, $optionValue) : $optionValue;
392
                // append th eption value to the array
393
                $attrs[] = sprintf('%s=%s', $attributeCode, $optionValue);
394
            }
395
        }
396
397
        // implode the array with the packed additional attributes and return it
398
        return $this->serialize($attrs);
399
    }
400
}
401