Completed
Push — pac-57--delete-eav-relation ( 96596e )
by
unknown
10:02
created

AttributeObserverTrait   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 663
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 1
dl 0
loc 663
rs 8.337
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttributeCode() 0 4 1
A getAttributeValue() 0 4 1
A getEmptyAttributeValueConstant() 0 4 1
C clearRow() 0 56 13
A getPrimaryKeyValue() 0 4 1
C process() 0 158 14
A prepareAttributes() 0 20 1
A initializeAttribute() 0 4 1
A loadRawEntity() 0 23 5
A initializeEntity() 0 16 4
A mergeEntity() 0 29 5
A getCallbacksByType() 0 4 1
A getBackendTypes() 0 4 1
A getAttributes() 0 4 1
getAttributesByPrimaryKeyAndStoreId() 0 1 ?
getSystemLogger() 0 1 ?
getPrimaryKey() 0 1 ?
getPrimaryKeyMemberName() 0 1 ?
getPrimaryKeyColumnName() 0 1 ?
storeViewHasBeenProcessed() 0 1 ?
persistVarcharAttribute() 0 1 ?
persistIntAttribute() 0 1 ?
persistDecimalAttribute() 0 1 ?
persistDatetimeAttribute() 0 1 ?
persistTextAttribute() 0 1 ?
deleteDatetimeAttribute() 0 1 ?
deleteDecimalAttribute() 0 1 ?
deleteIntAttribute() 0 1 ?
deleteTextAttribute() 0 1 ?
deleteVarcharAttribute() 0 1 ?
hasChanges() 0 1 ?
hasValue() 0 1 ?

How to fix   Complexity   

Complex Class

Complex classes like AttributeObserverTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AttributeObserverTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TechDivision\Import\Observers\AttributeObserverTrait
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2020 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Observers;
22
23
use TechDivision\Import\Utils\InputOptionKeysInterface;
24
use TechDivision\Import\Utils\LoggerKeys;
25
use TechDivision\Import\Utils\MemberNames;
26
use TechDivision\Import\Utils\StoreViewCodes;
27
use TechDivision\Import\Utils\OperationNames;
28
use TechDivision\Import\Utils\BackendTypeKeys;
29
use TechDivision\Import\Utils\ConfigurationKeys;
30
31
/**
32
 * Observer that creates/updates the EAV attributes.
33
 *
34
 * @author    Tim Wagner <[email protected]>
35
 * @copyright 2020 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/techdivision/import
38
 * @link      http://www.techdivision.com
39
 */
40
trait AttributeObserverTrait
41
{
42
43
    /**
44
     * The ID of the attribute to create the values for.
45
     *
46
     * @var integer
47
     */
48
    protected $attributeId;
49
50
    /**
51
     * The attribute code of the attribute to create the values for.
52
     *
53
     * @var string
54
     */
55
    protected $attributeCode;
56
57
    /**
58
     * The backend type of the attribute to create the values for.
59
     *
60
     * @var string
61
     */
62
    protected $backendType;
63
64
    /**
65
     * The attribute value in process.
66
     *
67
     * @var mixed
68
     */
69
    protected $attributeValue;
70
71
    /**
72
     * The array with the column keys that has to be cleaned up when their values are empty.
73
     *
74
     * @var array
75
     */
76
    protected $cleanUpEmptyColumnKeys;
77
78
    /**
79
     * The array with the default column values.
80
     *
81
     * @var array
82
     */
83
    protected $defaultColumnValues;
84
85
    /**
86
     * The attribute we're actually processing.
87
     *
88
     * @var array
89
     */
90
    protected $attribute;
91
92
    /**
93
     * The entity's existing attribues.
94
     *
95
     * @var array
96
     */
97
    protected $attributes;
98
99
    /**
100
     * The operation that has to be executed to update the attribute.
101
     *
102
     * @var string
103
     */
104
    protected $operation;
105
106
    /**
107
     * The attribute code that has to be processed.
108
     *
109
     * @return string The attribute code
110
     */
111
    public function getAttributeCode()
112
    {
113
        return $this->attributeCode;
114
    }
115
116
    /**
117
     * The attribute value that has to be processed.
118
     *
119
     * @return string The attribute value
120
     */
121
    public function getAttributeValue()
122
    {
123
        return $this->attributeValue;
124
    }
125
126
    /**
127
     * Get empty attribute value constant from global konfiguration
128
     *
129
     * @return string
130
     */
131
    private function getEmptyAttributeValueConstant()
132
    {
133
        return $this->getSubject()->getConfiguration()->getConfiguration()->getEmptyAttributeValueConstant();
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
134
    }
135
136
    /**
137
     * Remove all the empty values from the row and return the cleared row.
138
     *
139
     * @return array The cleared row
140
     */
141
    protected function clearRow()
142
    {
143
144
        // initialize the array with the column keys that has to be cleaned-up
145
        $this->cleanUpEmptyColumnKeys = array();
146
147
        // query whether or not column names that has to be cleaned up have been configured
148
        if ($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_EMPTY_COLUMNS)) {
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
149
            // if yes, load the column names
150
            $cleanUpEmptyColumns = $this->getSubject()->getCleanUpColumns();
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
151
152
            // translate the column names into column keys
153
            foreach ($cleanUpEmptyColumns as $cleanUpEmptyColumn) {
154
                if ($this->hasHeader($cleanUpEmptyColumn)) {
0 ignored issues
show
Bug introduced by
It seems like hasHeader() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
155
                    $this->cleanUpEmptyColumnKeys[$cleanUpEmptyColumn] = $this->getHeader($cleanUpEmptyColumn);
0 ignored issues
show
Bug introduced by
It seems like getHeader() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
156
                }
157
            }
158
        }
159
160
        // initialize the array with the default column values
161
        $this->defaultColumnValues = array();
162
163
        // iterate over the default column values to figure out whether or not the column exists
164
        $defaultColumnValues = $this->getSubject()->getDefaultColumnValues();
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
165
166
        // prepare the array with the default column values, BUT we only take
167
        // care of default columns WITHOUT any value, because in only in this
168
        // case the default EAV value from the DB should be used when a empty
169
        // column value has been found to create a NEW attribute value
170
        foreach ($defaultColumnValues as $columnName => $defaultColumnValue) {
171
            if ($defaultColumnValue === '') {
172
                $this->defaultColumnValues[$columnName] = $this->getHeader($columnName);
0 ignored issues
show
Bug introduced by
It seems like getHeader() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
173
            }
174
        }
175
176
        $emptyValueDefinition = $this->getEmptyAttributeValueConstant();
177
        // load the header keys
178
        $headers = in_array($emptyValueDefinition, $this->row, true) ? array_flip($this->getHeaders()) : [];
0 ignored issues
show
Bug introduced by
The property row does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
It seems like getHeaders() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
179
        // remove all the empty values from the row, expected the columns has to be cleaned-up
180
        foreach ($this->row as $key => $value) {
181
            if ($value === $emptyValueDefinition) {
182
                $this->cleanUpEmptyColumnKeys[$headers[$key]] = $key;
183
                $this->row[$key] = '';
184
            }
185
            // query whether or not the value is empty AND the column has NOT to be cleaned-up
186
            if (($value === null || $value === '') &&
187
                in_array($key, $this->cleanUpEmptyColumnKeys) === false &&
188
                in_array($key, $this->defaultColumnValues) === false
189
            ) {
190
                unset($this->row[$key]);
191
            }
192
        }
193
194
        // finally return the clean row
195
        return $this->row;
196
    }
197
198
    /**
199
     * Returns the value(s) of the primary key column(s). As the primary key column can
200
     * also consist of two columns, the return value can be an array also.
201
     *
202
     * @return mixed The primary key value(s)
203
     */
204
    protected function getPrimaryKeyValue()
205
    {
206
        return $this->getValue($this->getPrimaryKeyColumnName());
0 ignored issues
show
Bug introduced by
It seems like getValue() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
207
    }
208
209
    /**
210
     * Process the observer's business logic.
211
     *
212
     * @return void
213
     */
214
    protected function process()
215
    {
216
217
        // initialize the store view code
218
        $this->prepareStoreViewCode();
0 ignored issues
show
Bug introduced by
It seems like prepareStoreViewCode() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
219
220
        // load the store ID, use the admin store if NO store view code has been set
221
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
0 ignored issues
show
Bug introduced by
It seems like getRowStoreId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
222
223
        // load the entity's existing attributes
224
        $this->getAttributesByPrimaryKeyAndStoreId($this->getPrimaryKey(), $storeId);
225
226
        // load the store view - if no store view has been set, we assume the admin
227
        // store view, which will contain the default (fallback) attribute values
228
        $storeViewCode = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
229
230
        // query whether or not the row has already been processed
231
        if ($this->storeViewHasBeenProcessed($pk = $this->getPrimaryKeyValue(), $storeViewCode)) {
232
            // log a message
233
            $this->getSystemLogger()->warning(
234
                $this->appendExceptionSuffix(
0 ignored issues
show
Bug introduced by
It seems like appendExceptionSuffix() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
235
                    sprintf(
236
                        'Attributes for "%s" "%s" + store view code "%s" has already been processed',
237
                        $this->getPrimaryKeyColumnName(),
238
                        $pk,
239
                        $storeViewCode
240
                    )
241
                )
242
            );
243
244
            // return immediately
245
            return;
246
        }
247
248
        // load the attributes by the found attribute set and the backend types
249
        $attributes = $this->getAttributes();
250
        $backendTypes = $this->getBackendTypes();
251
252
        // load the header keys
253
        $headers = array_flip($this->getHeaders());
0 ignored issues
show
Bug introduced by
It seems like getHeaders() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
254
255
        // remove all the empty values from the row
256
        $row = $this->clearRow();
257
258
        // iterate over the attributes and append them to the row
259
        foreach ($row as $key => $attributeValue) {
260
            // query whether or not attribute with the found code exists
261
            if (!isset($attributes[$attributeCode = $headers[$key]])) {
262
                // log a message in debug mode
263
                if ($this->isDebugMode()) {
0 ignored issues
show
Bug introduced by
It seems like isDebugMode() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
264
                    $this->getSystemLogger()->debug(
265
                        $this->appendExceptionSuffix(
0 ignored issues
show
Bug introduced by
It seems like appendExceptionSuffix() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
266
                            sprintf(
267
                                'Can\'t find attribute with attribute code "%s"',
268
                                $attributeCode
269
                            )
270
                        )
271
                    );
272
                }
273
274
                // stop processing
275
                continue;
276
            } else {
277
                // log a message in debug mode
278
                if ($this->isDebugMode()) {
0 ignored issues
show
Bug introduced by
It seems like isDebugMode() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
279
                // log a message in debug mode
280
                    $this->getSystemLogger()->debug(
281
                        $this->appendExceptionSuffix(
0 ignored issues
show
Bug introduced by
It seems like appendExceptionSuffix() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
282
                            sprintf(
283
                                'Found attribute with attribute code "%s"',
284
                                $attributeCode
285
                            )
286
                        )
287
                    );
288
                }
289
            }
290
291
            // if yes, load the attribute by its code
292
            $this->attribute = $attributes[$attributeCode];
293
294
            // load the backend type => to find the apropriate entity
295
            $backendType = $this->attribute[MemberNames::BACKEND_TYPE];
296
            if ($backendType === null) {
297
                // log a message in debug mode
298
                $this->getSystemLogger()->warning(
299
                    $this->appendExceptionSuffix(
0 ignored issues
show
Bug introduced by
It seems like appendExceptionSuffix() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
300
                        sprintf(
301
                            'Found EMTPY backend type for attribute "%s"',
302
                            $attributeCode
303
                        )
304
                    )
305
                );
306
                // stop processing
307
                continue;
308
            }
309
310
            // do nothing on static backend type
311
            if ($backendType === BackendTypeKeys::BACKEND_TYPE_STATIC) {
312
                continue;
313
            }
314
315
            // query whether or not we've found a supported backend type
316
            if (isset($backendTypes[$backendType])) {
317
                // initialize attribute ID/code and backend type
318
                $this->backendType = $backendType;
319
                $this->attributeCode = $attributeCode;
320
                $this->attributeId = $this->attribute[MemberNames::ATTRIBUTE_ID];
321
322
                // set the attribute value as well as the original attribute value
323
                $this->attributeValue = $attributeValue;
324
325
                // initialize the persist method for the found backend type
326
                list ($persistMethod, , $deleteMethod) = $backendTypes[$backendType];
327
328
                // prepare the attribute vale and query whether or not it has to be persisted
329
                if ($this->hasChanges($value = $this->initializeAttribute($this->prepareAttributes()))) {
0 ignored issues
show
Bug introduced by
It seems like $this->prepareAttributes() targeting TechDivision\Import\Obse...it::prepareAttributes() can also be of type null; however, TechDivision\Import\Obse...::initializeAttribute() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
330
                    // query whether or not the entity's value has to be persisted or deleted. if the value is
331
                    // an empty string and the status is UPDATE, then the value exists and has to be deleted
332
                    // We need to user $attributeValue instead of $value[MemberNames::VALUE] in cases where
333
                    // value was casted by attribute type. E.g. special_price = 0 if value is empty string in CSV
334
                    switch ($this->operation) {
335
                        // create/update the attribute
336
                        case OperationNames::CREATE:
337
                        case OperationNames::UPDATE:
338
                            $this->$persistMethod($value);
339
                            break;
340
                        // delete the attribute
341
                        case OperationNames::DELETE:
342
                            $this->$deleteMethod(array(MemberNames::VALUE_ID => $value[MemberNames::VALUE_ID]));
343
                            break;
344
                        // skip the attribute
345
                        case OperationNames::SKIP:
346
                            $this->getSubject()->getSystemLogger()->debug(sprintf('Skipped processing attribute "%s"', $attributeCode));
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
347
                            break;
348
                        // should never happen
349
                        default:
350
                            $this->getSubject()->getSystemLogger()->debug(sprintf('Found invalid entity status "%s" for attribute "%s"', $value[MemberNames::VALUE] ?? 'NULL', $attributeCode));
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
351
                    }
352
                } else {
353
                    $this->getSubject()->getSystemLogger()->debug(sprintf('Skip to persist value for attribute "%s"', $attributeCode));
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
354
                }
355
356
                // continue with the next value
357
                continue;
358
            }
359
360
            // log the debug message
361
            $this->getSystemLogger()->debug(
362
                $this->getSubject()->appendExceptionSuffix(
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
363
                    sprintf(
364
                        'Found invalid backend type %s for attribute "%s"',
365
                        $backendType,
366
                        $attributeCode
367
                    )
368
                )
369
            );
370
        }
371
    }
372
373
    /**
374
     * Prepare the attributes of the entity that has to be persisted.
375
     *
376
     * @return array|null The prepared attributes
377
     */
378
    protected function prepareAttributes()
379
    {
380
381
        // load the ID of the product that has been created recently
382
        $lastEntityId = $this->getPrimaryKey();
383
384
        // load the store ID, use the admin store if NO store view code has been set
385
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
0 ignored issues
show
Bug introduced by
It seems like getRowStoreId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
386
387
        // prepare the attribute values
388
        return $this->initializeEntity(
389
            $this->loadRawEntity(
390
                array(
391
                   $this->getPrimaryKeyMemberName() => $lastEntityId,
392
                    MemberNames::ATTRIBUTE_ID       => $this->attributeId,
393
                    MemberNames::STORE_ID           => $storeId
394
                )
395
            )
396
        );
397
    }
398
399
    /**
400
     * Initialize the category product with the passed attributes and returns an instance.
401
     *
402
     * @param array $attr The category product attributes
403
     *
404
     * @return array The initialized category product
405
     */
406
    protected function initializeAttribute(array $attr)
407
    {
408
        return $attr;
409
    }
410
411
    /**
412
     * Load's and return's a raw customer entity without primary key but the mandatory members only and nulled values.
413
     *
414
     * @param array $data An array with data that will be used to initialize the raw entity with
415
     *
416
     * @return array The initialized entity
417
     */
418
    protected function loadRawEntity(array $data = array())
419
    {
420
421
        // laod the callbacks for the actual attribute code
422
        $callbacks = $this->getCallbacksByType($this->attributeCode);
423
424
        // invoke the pre-cast callbacks
425
        foreach ($callbacks as $callback) {
426
            $this->attributeValue = $callback->handle($this);
427
        }
428
429
        // load the default value
430
        $defaultValue = isset($this->attribute[MemberNames::DEFAULT_VALUE]) ? $this->attribute[MemberNames::DEFAULT_VALUE] : '';
431
432
        // load the value that has to be casted
433
        $value = $this->attributeValue === '' || $this->attributeValue === null ? $defaultValue : $this->attributeValue;
434
435
        // cast the value
436
        $castedValue = $this->castValueByBackendType($this->backendType, $value);
0 ignored issues
show
Bug introduced by
It seems like castValueByBackendType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
437
438
        // merge the casted value into the passed data and return it
439
        return array_merge(array(MemberNames::VALUE => $castedValue), $data);
440
    }
441
442
    /**
443
     * Initialize's and return's a new entity with the status 'create'.
444
     *
445
     * @param array $attr The attributes to merge into the new entity
446
     *
447
     * @return array The initialized entity
448
     */
449
    protected function initializeEntity(array $attr = array())
450
    {
451
452
        // initialize the operation name
453
        $this->operation = OperationNames::CREATE;
454
455
        // query whether or not the colunm IS empty and it is NOT in the
456
        // array with the default column values, because in that case we
457
        // want to skip processing the attribute
458
        if (array_key_exists($this->attributeCode, $this->defaultColumnValues) === false && ($this->attributeValue === '' || $this->attributeValue == null)) {
459
            $this->operation = OperationNames::SKIP;
460
        }
461
462
        // initialize the entity with the passed data
463
        return parent::initializeEntity($attr);
464
    }
465
466
    /**
467
     * Merge's and return's the entity with the passed attributes and set's the
468
     * passed status.
469
     *
470
     * @param array       $entity        The entity to merge the attributes into
471
     * @param array       $attr          The attributes to be merged
472
     * @param string|null $changeSetName The change set name to use
473
     *
474
     * @return array The merged entity
475
     */
476
    protected function mergeEntity(array $entity, array $attr, $changeSetName = null)
477
    {
478
479
        // we want to update the attribute, if we're here
480
        $this->operation = OperationNames::UPDATE;
481
482
        // query whether or not the column is EMPTY
483
        if ($this->attributeValue === '' || $this->attributeValue === null) {
484
            // if the value is empty AND it is IN the array with default column values
485
            // BUT it is NOT in the array with columns we want to clean-up the default
486
            // column value has to be removed, because we do NOT want to override the
487
            // value existing in the database
488
            if (array_key_exists($this->attributeCode, $this->defaultColumnValues) &&
489
                array_key_exists($this->attributeCode, $this->cleanUpEmptyColumnKeys) === false
490
            ) {
491
                // remove the value from the array with the column values, because
492
                // this is the default value from the database and it should NOT
493
                // override the value from the entity in that case
494
                unset($attr[MemberNames::VALUE]);
495
            } else {
496
                // otherwise keep the value and DELETE the whole attribute from
497
                // the database
498
                $this->operation = OperationNames::DELETE;
499
            }
500
        }
501
502
        // merge and return the data
503
        return parent::mergeEntity($entity, $attr, $changeSetName);
504
    }
505
506
    /**
507
     * Return's the array with callbacks for the passed type.
508
     *
509
     * @param string $type The type of the callbacks to return
510
     *
511
     * @return array The callbacks
512
     */
513
    protected function getCallbacksByType($type)
514
    {
515
        return $this->getSubject()->getCallbacksByType($type);
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
516
    }
517
518
    /**
519
     * Return's mapping for the supported backend types (for the product entity) => persist methods.
520
     *
521
     * @return array The mapping for the supported backend types
522
     */
523
    protected function getBackendTypes()
524
    {
525
        return $this->getSubject()->getBackendTypes();
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
526
    }
527
528
    /**
529
     * Return's the attributes for the attribute set of the product that has to be created.
530
     *
531
     * @return array The attributes
532
     * @throws \Exception
533
     */
534
    protected function getAttributes()
535
    {
536
        return $this->getSubject()->getAttributes();
0 ignored issues
show
Bug introduced by
It seems like getSubject() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
537
    }
538
539
    /**
540
     * Intializes the existing attributes for the entity with the passed primary key.
541
     *
542
     * @param string  $pk      The primary key of the entity to load the attributes for
543
     * @param integer $storeId The ID of the store view to load the attributes for
544
     *
545
     * @return array The entity attributes
546
     */
547
    abstract protected function getAttributesByPrimaryKeyAndStoreId($pk, $storeId);
548
549
    /**
550
     * Return's the logger with the passed name, by default the system logger.
551
     *
552
     * @param string $name The name of the requested system logger
553
     *
554
     * @return \Psr\Log\LoggerInterface The logger instance
555
     * @throws \Exception Is thrown, if the requested logger is NOT available
556
     */
557
    abstract protected function getSystemLogger($name = LoggerKeys::SYSTEM);
558
559
    /**
560
     * Return's the PK to create the product => attribute relation.
561
     *
562
     * @return integer The PK to create the relation with
563
     */
564
    abstract protected function getPrimaryKey();
565
566
    /**
567
     * Return's the PK column name to create the product => attribute relation.
568
     *
569
     * @return string The PK column name
570
     */
571
    abstract protected function getPrimaryKeyMemberName();
572
573
    /**
574
     * Return's the column name that contains the primary key.
575
     *
576
     * @return string the column name that contains the primary key
577
     */
578
    abstract protected function getPrimaryKeyColumnName();
579
580
    /**
581
     * Queries whether or not the passed PK and store view code has already been processed.
582
     *
583
     * @param string $pk            The PK to check been processed
584
     * @param string $storeViewCode The store view code to check been processed
585
     *
586
     * @return boolean TRUE if the PK and store view code has been processed, else FALSE
587
     */
588
    abstract protected function storeViewHasBeenProcessed($pk, $storeViewCode);
589
590
    /**
591
     * Persist's the passed varchar attribute.
592
     *
593
     * @param array $attribute The attribute to persist
594
     *
595
     * @return void
596
     */
597
    abstract protected function persistVarcharAttribute($attribute);
598
599
    /**
600
     * Persist's the passed integer attribute.
601
     *
602
     * @param array $attribute The attribute to persist
603
     *
604
     * @return void
605
     */
606
    abstract protected function persistIntAttribute($attribute);
607
608
    /**
609
     * Persist's the passed decimal attribute.
610
     *
611
     * @param array $attribute The attribute to persist
612
     *
613
     * @return void
614
     */
615
    abstract protected function persistDecimalAttribute($attribute);
616
617
    /**
618
     * Persist's the passed datetime attribute.
619
     *
620
     * @param array $attribute The attribute to persist
621
     *
622
     * @return void
623
     */
624
    abstract protected function persistDatetimeAttribute($attribute);
625
626
    /**
627
     * Persist's the passed text attribute.
628
     *
629
     * @param array $attribute The attribute to persist
630
     *
631
     * @return void
632
     */
633
    abstract protected function persistTextAttribute($attribute);
634
635
    /**
636
     * Delete's the datetime attribute with the passed value ID.
637
     *
638
     * @param array       $row  The attributes of the entity to delete
639
     * @param string|null $name The name of the prepared statement that has to be executed
640
     *
641
     * @return void
642
     */
643
    abstract protected function deleteDatetimeAttribute(array $row, $name = null);
644
645
    /**
646
     * Delete's the decimal attribute with the passed value ID.
647
     *
648
     * @param array       $row  The attributes of the entity to delete
649
     * @param string|null $name The name of the prepared statement that has to be executed
650
     *
651
     * @return void
652
     */
653
    abstract protected function deleteDecimalAttribute(array $row, $name = null);
654
655
    /**
656
     * Delete's the integer attribute with the passed value ID.
657
     *
658
     * @param array       $row  The attributes of the entity to delete
659
     * @param string|null $name The name of the prepared statement that has to be executed
660
     *
661
     * @return void
662
     */
663
    abstract protected function deleteIntAttribute(array $row, $name = null);
664
665
    /**
666
     * Delete's the text attribute with the passed value ID.
667
     *
668
     * @param array       $row  The attributes of the entity to delete
669
     * @param string|null $name The name of the prepared statement that has to be executed
670
     *
671
     * @return void
672
     */
673
    abstract protected function deleteTextAttribute(array $row, $name = null);
674
675
    /**
676
     * Delete's the varchar attribute with the passed value ID.
677
     *
678
     * @param array       $row  The attributes of the entity to delete
679
     * @param string|null $name The name of the prepared statement that has to be executed
680
     *
681
     * @return void
682
     */
683
    abstract protected function deleteVarcharAttribute(array $row, $name = null);
684
685
    /**
686
     * Query whether or not the entity has to be processed.
687
     *
688
     * @param array $entity The entity to query for
689
     *
690
     * @return boolean TRUE if the entity has to be processed, else FALSE
691
     */
692
    abstract protected function hasChanges(array $entity);
693
694
    /**
695
     * Query whether or not a value for the column with the passed name exists.
696
     *
697
     * @param string $name The column name to query for a valid value
698
     *
699
     * @return boolean TRUE if the value is set, else FALSE
700
     */
701
    abstract protected function hasValue($name);
702
}
703