Completed
Push — master ( e77248...65c04d )
by
unknown
04:36 queued 02:44
created

AttributeObserverTrait   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 670
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 80.56%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 1
dl 0
loc 670
ccs 116
cts 144
cp 0.8056
rs 6.89
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttributeCode() 0 4 1
A getAttributeValue() 0 4 1
A getPrimaryKeyValue() 0 4 1
A getEmptyAttributeValueConstant() 0 4 1
C clearRow() 0 56 13
D process() 0 165 17
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\LoggerKeys;
24
use TechDivision\Import\Utils\MemberNames;
25
use TechDivision\Import\Utils\StoreViewCodes;
26
use TechDivision\Import\Utils\OperationNames;
27
use TechDivision\Import\Utils\BackendTypeKeys;
28
use TechDivision\Import\Utils\ConfigurationKeys;
29
30
/**
31
 * Observer that creates/updates the EAV attributes.
32
 *
33
 * @author    Tim Wagner <[email protected]>
34
 * @copyright 2020 TechDivision GmbH <[email protected]>
35
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
36
 * @link      https://github.com/techdivision/import
37
 * @link      http://www.techdivision.com
38
 */
39
trait AttributeObserverTrait
40
{
41
42
    /**
43
     * The ID of the attribute to create the values for.
44
     *
45
     * @var integer
46
     */
47
    protected $attributeId;
48
49
    /**
50
     * The attribute code of the attribute to create the values for.
51
     *
52
     * @var string
53
     */
54
    protected $attributeCode;
55
56
    /**
57
     * The backend type of the attribute to create the values for.
58
     *
59
     * @var string
60
     */
61
    protected $backendType;
62
63
    /**
64
     * The attribute value in process.
65
     *
66
     * @var mixed
67
     */
68
    protected $attributeValue;
69
70
    /**
71
     * The array with the column keys that has to be cleaned up when their values are empty.
72
     *
73
     * @var array
74
     */
75
    protected $cleanUpEmptyColumnKeys;
76
77
    /**
78
     * The array with the default column values.
79
     *
80
     * @var array
81
     */
82
    protected $defaultColumnValues;
83
84
    /**
85
     * The attribute we're actually processing.
86
     *
87
     * @var array
88
     */
89
    protected $attribute;
90
91
    /**
92
     * The entity's existing attribues.
93
     *
94
     * @var array
95
     */
96
    protected $attributes;
97
98
    /**
99
     * The operation that has to be executed to update the attribute.
100
     *
101
     * @var string
102
     */
103
    protected $operation;
104
105
    /**
106
     * The attribute code that has to be processed.
107
     *
108
     * @return string The attribute code
109
     */
110 1
    public function getAttributeCode()
111
    {
112 1
        return $this->attributeCode;
113
    }
114
115
    /**
116
     * The attribute value that has to be processed.
117
     *
118
     * @return string The attribute value
119
     */
120 1
    public function getAttributeValue()
121
    {
122 1
        return $this->attributeValue;
123
    }
124
125
    /**
126
     * Get empty attribute value constant from global konfiguration
127
     *
128
     * @return string
129
     */
130 7
    private function getEmptyAttributeValueConstant()
131
    {
132 7
        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...
133
    }
134
135
    /**
136
     * Remove all the empty values from the row and return the cleared row.
137
     *
138
     * @return array The cleared row
139
     */
140 7
    protected function clearRow()
141
    {
142
143
        // initialize the array with the column keys that has to be cleaned-up
144 7
        $this->cleanUpEmptyColumnKeys = array();
145
146
        // query whether or not column names that has to be cleaned up have been configured
147 7
        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...
148
            // if yes, load the column names
149
            $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...
150
151
            // translate the column names into column keys
152
            foreach ($cleanUpEmptyColumns as $cleanUpEmptyColumn) {
153
                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...
154
                    $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...
155
                }
156
            }
157
        }
158
159
        // initialize the array with the default column values
160 7
        $this->defaultColumnValues = array();
161
162
        // iterate over the default column values to figure out whether or not the column exists
163 7
        $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...
164
165
        // prepare the array with the default column values, BUT we only take
166
        // care of default columns WITHOUT any value, because in only in this
167
        // case the default EAV value from the DB should be used when a empty
168
        // column value has been found to create a NEW attribute value
169 7
        foreach ($defaultColumnValues as $columnName => $defaultColumnValue) {
170
            if ($defaultColumnValue === '') {
171
                $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...
172
            }
173
        }
174
175 7
        $emptyValueDefinition = $this->getEmptyAttributeValueConstant();
176
        // load the header keys
177 7
        $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...
178
        // remove all the empty values from the row, expected the columns has to be cleaned-up
179 7
        foreach ($this->row as $key => $value) {
180 7
            if ($value === $emptyValueDefinition) {
181
                $this->cleanUpEmptyColumnKeys[$headers[$key]] = $key;
182
                $this->row[$key] = '';
183
            }
184
            // query whether or not the value is empty AND the column has NOT to be cleaned-up
185 7
            if (($value === null || $value === '') &&
186 7
                in_array($key, $this->cleanUpEmptyColumnKeys) === false &&
187 7
                in_array($key, $this->defaultColumnValues) === false
188
            ) {
189 1
                unset($this->row[$key]);
190
            }
191
        }
192
193
        // finally return the clean row
194 7
        return $this->row;
195
    }
196
197
    /**
198
     * Returns the value(s) of the primary key column(s). As the primary key column can
199
     * also consist of two columns, the return value can be an array also.
200
     *
201
     * @return mixed The primary key value(s)
202
     */
203 7
    protected function getPrimaryKeyValue()
204
    {
205 7
        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...
206
    }
207
208
    /**
209
     * Process the observer's business logic.
210
     *
211
     * @return void
212
     */
213 7
    protected function process()
214
    {
215
216
        // initialize the store view code
217 7
        $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...
218
219
        // load the store ID, use the admin store if NO store view code has been set
220 7
        $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...
221
222
        // load the entity's existing attributes
223 7
        $this->getAttributesByPrimaryKeyAndStoreId($this->getPrimaryKey(), $storeId);
224
225
        // load the store view - if no store view has been set, we assume the admin
226
        // store view, which will contain the default (fallback) attribute values
227 7
        $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...
228
229
        // query whether or not the row has already been processed
230 7
        if ($this->storeViewHasBeenProcessed($pk = $this->getPrimaryKeyValue(), $storeViewCode)) {
231
            // log a message
232
            $this->getSystemLogger()->warning(
233
                $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...
234
                    sprintf(
235
                        'Attributes for "%s" "%s" + store view code "%s" has already been processed',
236
                        $this->getPrimaryKeyColumnName(),
237
                        $pk,
238
                        $storeViewCode
239
                    )
240
                )
241
            );
242
243
            // return immediately
244
            return;
245
        }
246
247
        // load the attributes by the found attribute set and the backend types
248 7
        $attributes = $this->getAttributes();
249 7
        $backendTypes = $this->getBackendTypes();
250
251
        // load the header keys
252 7
        $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...
253
254
        // remove all the empty values from the row
255 7
        $row = $this->clearRow();
256
257
        // iterate over the attributes and append them to the row
258 7
        foreach ($row as $key => $attributeValue) {
259
            // query whether or not attribute with the found code exists
260 6
            if (!isset($attributes[$attributeCode = $headers[$key]])) {
261
                // log a message in debug mode
262 1
                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...
263 1
                    $this->getSystemLogger()->debug(
264 1
                        $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...
265 1
                            sprintf(
266 1
                                'Can\'t find attribute with attribute code "%s"',
267 1
                                $attributeCode
268
                            )
269
                        )
270
                    );
271
                }
272
273
                // stop processing
274 1
                continue;
275
            } else {
276
                // log a message in debug mode
277 5
                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...
278
                // log a message in debug mode
279 2
                    $this->getSystemLogger()->debug(
280 2
                        $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...
281 2
                            sprintf(
282 2
                                'Found attribute with attribute code "%s"',
283 2
                                $attributeCode
284
                            )
285
                        )
286
                    );
287
                }
288
            }
289
290
            // if yes, load the attribute by its code
291 5
            $this->attribute = $attributes[$attributeCode];
292
293
            // load the backend type => to find the apropriate entity
294 5
            $backendType = $this->attribute[MemberNames::BACKEND_TYPE];
295 5
            if ($backendType === null) {
296
                // log a message in debug mode
297 1
                $this->getSystemLogger()->warning(
298 1
                    $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...
299 1
                        sprintf(
300 1
                            'Found EMTPY backend type for attribute "%s"',
301 1
                            $attributeCode
302
                        )
303
                    )
304
                );
305
                // stop processing
306 1
                continue;
307
            }
308
309
            // do nothing on static backend type
310 4
            if ($backendType === BackendTypeKeys::BACKEND_TYPE_STATIC) {
311 1
                continue;
312
            }
313
314
            // do nothing in non-default stores with global attributes
315 3
            if (isset($attribute[MemberNames::IS_GLOBAL]) &&
316 3
                $attribute[MemberNames::IS_GLOBAL] == 1 &&
317 3
                $storeId !== 0) {
318
                continue;
319
            }
320
321
            // query whether or not we've found a supported backend type
322 3
            if (isset($backendTypes[$backendType])) {
323
                // initialize attribute ID/code and backend type
324 2
                $this->backendType = $backendType;
325 2
                $this->attributeCode = $attributeCode;
326 2
                $this->attributeId = $this->attribute[MemberNames::ATTRIBUTE_ID];
327
328
                // set the attribute value as well as the original attribute value
329 2
                $this->attributeValue = $attributeValue;
330
331
                // initialize the persist method for the found backend type
332 2
                list ($persistMethod, , $deleteMethod) = $backendTypes[$backendType];
333
334
                // prepare the attribute vale and query whether or not it has to be persisted
335 2
                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...
336
                    // query whether or not the entity's value has to be persisted or deleted. if the value is
337
                    // an empty string and the status is UPDATE, then the value exists and has to be deleted
338
                    // We need to user $attributeValue instead of $value[MemberNames::VALUE] in cases where
339
                    // value was casted by attribute type. E.g. special_price = 0 if value is empty string in CSV
340 2
                    switch ($this->operation) {
341
                        // create/update the attribute
342 2
                        case OperationNames::CREATE:
343 1
                        case OperationNames::UPDATE:
344 1
                            $this->$persistMethod($value);
345 1
                            break;
346
                        // delete the attribute
347 1
                        case OperationNames::DELETE:
348
                            $this->$deleteMethod(array(MemberNames::VALUE_ID => $value[MemberNames::VALUE_ID]));
349
                            break;
350
                        // skip the attribute
351 1
                        case OperationNames::SKIP:
352 1
                            $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...
353 1
                            break;
354
                        // should never happen
355
                        default:
356 2
                            $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...
357
                    }
358
                } else {
359
                    $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...
360
                }
361
362
                // continue with the next value
363 2
                continue;
364
            }
365
366
            // log the debug message
367 1
            $this->getSystemLogger()->debug(
368 1
                $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...
369 1
                    sprintf(
370 1
                        'Found invalid backend type %s for attribute "%s"',
371 1
                        $backendType,
372 1
                        $attributeCode
373
                    )
374
                )
375
            );
376
        }
377 7
    }
378
379
    /**
380
     * Prepare the attributes of the entity that has to be persisted.
381
     *
382
     * @return array|null The prepared attributes
383
     */
384 2
    protected function prepareAttributes()
385
    {
386
387
        // load the ID of the product that has been created recently
388 2
        $lastEntityId = $this->getPrimaryKey();
389
390
        // load the store ID, use the admin store if NO store view code has been set
391 2
        $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...
392
393
        // prepare the attribute values
394 2
        return $this->initializeEntity(
395 2
            $this->loadRawEntity(
396
                array(
397 2
                   $this->getPrimaryKeyMemberName() => $lastEntityId,
398 2
                    MemberNames::ATTRIBUTE_ID       => $this->attributeId,
399 2
                    MemberNames::STORE_ID           => $storeId
400
                )
401
            )
402
        );
403
    }
404
405
    /**
406
     * Initialize the category product with the passed attributes and returns an instance.
407
     *
408
     * @param array $attr The category product attributes
409
     *
410
     * @return array The initialized category product
411
     */
412 2
    protected function initializeAttribute(array $attr)
413
    {
414 2
        return $attr;
415
    }
416
417
    /**
418
     * Load's and return's a raw customer entity without primary key but the mandatory members only and nulled values.
419
     *
420
     * @param array $data An array with data that will be used to initialize the raw entity with
421
     *
422
     * @return array The initialized entity
423
     */
424 2
    protected function loadRawEntity(array $data = array())
425
    {
426
427
        // laod the callbacks for the actual attribute code
428 2
        $callbacks = $this->getCallbacksByType($this->attributeCode);
429
430
        // invoke the pre-cast callbacks
431 2
        foreach ($callbacks as $callback) {
432 1
            $this->attributeValue = $callback->handle($this);
433
        }
434
435
        // load the default value
436 2
        $defaultValue = isset($this->attribute[MemberNames::DEFAULT_VALUE]) ? $this->attribute[MemberNames::DEFAULT_VALUE] : '';
437
438
        // load the value that has to be casted
439 2
        $value = $this->attributeValue === '' || $this->attributeValue === null ? $defaultValue : $this->attributeValue;
440
441
        // cast the value
442 2
        $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...
443
444
        // merge the casted value into the passed data and return it
445 2
        return array_merge(array(MemberNames::VALUE => $castedValue), $data);
446
    }
447
448
    /**
449
     * Initialize's and return's a new entity with the status 'create'.
450
     *
451
     * @param array $attr The attributes to merge into the new entity
452
     *
453
     * @return array The initialized entity
454
     */
455 2
    protected function initializeEntity(array $attr = array())
456
    {
457
458
        // initialize the operation name
459 2
        $this->operation = OperationNames::CREATE;
460
461
        // query whether or not the colunm IS empty and it is NOT in the
462
        // array with the default column values, because in that case we
463
        // want to skip processing the attribute
464 2
        if (array_key_exists($this->attributeCode, $this->defaultColumnValues) === false && ($this->attributeValue === '' || $this->attributeValue == null)) {
465 1
            $this->operation = OperationNames::SKIP;
466
        }
467
468
        // initialize the entity with the passed data
469 2
        return parent::initializeEntity($attr);
470
    }
471
472
    /**
473
     * Merge's and return's the entity with the passed attributes and set's the
474
     * passed status.
475
     *
476
     * @param array       $entity        The entity to merge the attributes into
477
     * @param array       $attr          The attributes to be merged
478
     * @param string|null $changeSetName The change set name to use
479
     *
480
     * @return array The merged entity
481
     */
482
    protected function mergeEntity(array $entity, array $attr, $changeSetName = null)
483
    {
484
485
        // we want to update the attribute, if we're here
486
        $this->operation = OperationNames::UPDATE;
487
488
        // query whether or not the column is EMPTY
489
        if ($this->attributeValue === '' || $this->attributeValue === null) {
490
            // if the value is empty AND it is IN the array with default column values
491
            // BUT it is NOT in the array with columns we want to clean-up the default
492
            // column value has to be removed, because we do NOT want to override the
493
            // value existing in the database
494
            if (array_key_exists($this->attributeCode, $this->defaultColumnValues) &&
495
                array_key_exists($this->attributeCode, $this->cleanUpEmptyColumnKeys) === false
496
            ) {
497
                // remove the value from the array with the column values, because
498
                // this is the default value from the database and it should NOT
499
                // override the value from the entity in that case
500
                unset($attr[MemberNames::VALUE]);
501
            } else {
502
                // otherwise keep the value and DELETE the whole attribute from
503
                // the database
504
                $this->operation = OperationNames::DELETE;
505
            }
506
        }
507
508
        // merge and return the data
509
        return parent::mergeEntity($entity, $attr, $changeSetName);
510
    }
511
512
    /**
513
     * Return's the array with callbacks for the passed type.
514
     *
515
     * @param string $type The type of the callbacks to return
516
     *
517
     * @return array The callbacks
518
     */
519 2
    protected function getCallbacksByType($type)
520
    {
521 2
        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...
522
    }
523
524
    /**
525
     * Return's mapping for the supported backend types (for the product entity) => persist methods.
526
     *
527
     * @return array The mapping for the supported backend types
528
     */
529 7
    protected function getBackendTypes()
530
    {
531 7
        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...
532
    }
533
534
    /**
535
     * Return's the attributes for the attribute set of the product that has to be created.
536
     *
537
     * @return array The attributes
538
     * @throws \Exception
539
     */
540 7
    protected function getAttributes()
541
    {
542 7
        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...
543
    }
544
545
    /**
546
     * Intializes the existing attributes for the entity with the passed primary key.
547
     *
548
     * @param string  $pk      The primary key of the entity to load the attributes for
549
     * @param integer $storeId The ID of the store view to load the attributes for
550
     *
551
     * @return array The entity attributes
552
     */
553
    abstract protected function getAttributesByPrimaryKeyAndStoreId($pk, $storeId);
554
555
    /**
556
     * Return's the logger with the passed name, by default the system logger.
557
     *
558
     * @param string $name The name of the requested system logger
559
     *
560
     * @return \Psr\Log\LoggerInterface The logger instance
561
     * @throws \Exception Is thrown, if the requested logger is NOT available
562
     */
563
    abstract protected function getSystemLogger($name = LoggerKeys::SYSTEM);
564
565
    /**
566
     * Return's the PK to create the product => attribute relation.
567
     *
568
     * @return integer The PK to create the relation with
569
     */
570
    abstract protected function getPrimaryKey();
571
572
    /**
573
     * Return's the PK column name to create the product => attribute relation.
574
     *
575
     * @return string The PK column name
576
     */
577
    abstract protected function getPrimaryKeyMemberName();
578
579
    /**
580
     * Return's the column name that contains the primary key.
581
     *
582
     * @return string the column name that contains the primary key
583
     */
584
    abstract protected function getPrimaryKeyColumnName();
585
586
    /**
587
     * Queries whether or not the passed PK and store view code has already been processed.
588
     *
589
     * @param string $pk            The PK to check been processed
590
     * @param string $storeViewCode The store view code to check been processed
591
     *
592
     * @return boolean TRUE if the PK and store view code has been processed, else FALSE
593
     */
594
    abstract protected function storeViewHasBeenProcessed($pk, $storeViewCode);
595
596
    /**
597
     * Persist's the passed varchar attribute.
598
     *
599
     * @param array $attribute The attribute to persist
600
     *
601
     * @return void
602
     */
603
    abstract protected function persistVarcharAttribute($attribute);
604
605
    /**
606
     * Persist's the passed integer attribute.
607
     *
608
     * @param array $attribute The attribute to persist
609
     *
610
     * @return void
611
     */
612
    abstract protected function persistIntAttribute($attribute);
613
614
    /**
615
     * Persist's the passed decimal attribute.
616
     *
617
     * @param array $attribute The attribute to persist
618
     *
619
     * @return void
620
     */
621
    abstract protected function persistDecimalAttribute($attribute);
622
623
    /**
624
     * Persist's the passed datetime attribute.
625
     *
626
     * @param array $attribute The attribute to persist
627
     *
628
     * @return void
629
     */
630
    abstract protected function persistDatetimeAttribute($attribute);
631
632
    /**
633
     * Persist's the passed text attribute.
634
     *
635
     * @param array $attribute The attribute to persist
636
     *
637
     * @return void
638
     */
639
    abstract protected function persistTextAttribute($attribute);
640
641
    /**
642
     * Delete's the datetime attribute with the passed value ID.
643
     *
644
     * @param array       $row  The attributes of the entity to delete
645
     * @param string|null $name The name of the prepared statement that has to be executed
646
     *
647
     * @return void
648
     */
649
    abstract protected function deleteDatetimeAttribute(array $row, $name = null);
650
651
    /**
652
     * Delete's the decimal attribute with the passed value ID.
653
     *
654
     * @param array       $row  The attributes of the entity to delete
655
     * @param string|null $name The name of the prepared statement that has to be executed
656
     *
657
     * @return void
658
     */
659
    abstract protected function deleteDecimalAttribute(array $row, $name = null);
660
661
    /**
662
     * Delete's the integer attribute with the passed value ID.
663
     *
664
     * @param array       $row  The attributes of the entity to delete
665
     * @param string|null $name The name of the prepared statement that has to be executed
666
     *
667
     * @return void
668
     */
669
    abstract protected function deleteIntAttribute(array $row, $name = null);
670
671
    /**
672
     * Delete's the text attribute with the passed value ID.
673
     *
674
     * @param array       $row  The attributes of the entity to delete
675
     * @param string|null $name The name of the prepared statement that has to be executed
676
     *
677
     * @return void
678
     */
679
    abstract protected function deleteTextAttribute(array $row, $name = null);
680
681
    /**
682
     * Delete's the varchar attribute with the passed value ID.
683
     *
684
     * @param array       $row  The attributes of the entity to delete
685
     * @param string|null $name The name of the prepared statement that has to be executed
686
     *
687
     * @return void
688
     */
689
    abstract protected function deleteVarcharAttribute(array $row, $name = null);
690
691
    /**
692
     * Query whether or not the entity has to be processed.
693
     *
694
     * @param array $entity The entity to query for
695
     *
696
     * @return boolean TRUE if the entity has to be processed, else FALSE
697
     */
698
    abstract protected function hasChanges(array $entity);
699
700
    /**
701
     * Query whether or not a value for the column with the passed name exists.
702
     *
703
     * @param string $name The column name to query for a valid value
704
     *
705
     * @return boolean TRUE if the value is set, else FALSE
706
     */
707
    abstract protected function hasValue($name);
708
}
709