Completed
Pull Request — master (#105)
by Tim
11:17
created

AttributeObserverTrait::clearRow()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 1
nop 0
crap 2
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 2016 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\BackendTypeKeys;
27
28
/**
29
 * Observer that creates/updates the EAV attributes.
30
 *
31
 * @author    Tim Wagner <[email protected]>
32
 * @copyright 2016 TechDivision GmbH <[email protected]>
33
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
34
 * @link      https://github.com/techdivision/import
35
 * @link      http://www.techdivision.com
36
 */
37
trait AttributeObserverTrait
38
{
39
40
    /**
41
     * The ID of the attribute to create the values for.
42
     *
43
     * @var integer
44
     */
45
    protected $attributeId;
46
47
    /**
48
     * The attribute code of the attribute to create the values for.
49
     *
50
     * @var string
51
     */
52
    protected $attributeCode;
53
54
    /**
55
     * The backend type of the attribute to create the values for.
56
     *
57
     * @var string
58
     */
59
    protected $backendType;
60
61
    /**
62
     * The attribute value to process.
63
     *
64
     * @var mixed
65
     */
66
    protected $attributeValue;
67
68
    /**
69
     * The attribute code that has to be processed.
70
     *
71
     * @return string The attribute code
72
     */
73 1
    public function getAttributeCode()
74
    {
75 1
        return $this->attributeCode;
76
    }
77
78
    /**
79
     * The attribute value that has to be processed.
80
     *
81
     * @return string The attribute value
82
     */
83 1
    public function getAttributeValue()
84
    {
85 1
        return $this->attributeValue;
86
    }
87
88
    /**
89
     * Remove all the empty values from the row and return the cleared row.
90
     *
91
     * @return array The cleared row
92
     */
93 7
    protected function clearRow()
94
    {
95
96
        // remove all the empty values from the row
97 7
        return array_filter(
98 7
            $this->row,
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...
99 7
            function ($value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100 7
                return ($value !== null && $value !== '');
101 7
            },
102
            ARRAY_FILTER_USE_BOTH
103 7
        );
104
    }
105
106
    /**
107
     * Process the observer's business logic.
108
     *
109
     * @return void
110
     */
111 7
    protected function process()
112
    {
113
114
        // initialize the store view code
115 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...
116
117
        // load the PK
118 7
        $pk = $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...
119
120
        // load the store view - if no store view has been set, we assume the admin
121
        // store view, which will contain the default (fallback) attribute values
122 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...
123
124
        // query whether or not the row has already been processed
125 7
        if ($this->storeViewHasBeenProcessed($pk, $storeViewCode)) {
126
            // log a message
127
            $this->getSystemLogger()
128
                 ->warning(
129
                     sprintf(
130
                         'Attributes for %s "%s" + store view code "%s" has already been processed',
131
                         $this->getPrimaryKeyColumnName(),
132
                         $pk,
133
                         $storeViewCode
134
                     )
135
                 );
136
137
            // return immediately
138
            return;
139
        }
140
141
        // load the attributes by the found attribute set and the backend types
142 7
        $attributes = $this->getAttributes();
143 7
        $backendTypes = $this->getBackendTypes();
144
145
        // load the header keys
146 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...
147
148
        // remove all the empty values from the row
149 7
        $row = $this->clearRow();
150
151
        // iterate over the attributes and append them to the row
152 7
        foreach ($row as $key => $attributeValue) {
153
            // query whether or not attribute with the found code exists
154 6
            if (!isset($attributes[$attributeCode = $headers[$key]])) {
155
                // log a message in debug mode
156 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...
157 1
                    $this->getSystemLogger()->debug(
158 1
                        sprintf(
159 1
                            'Can\'t find attribute with attribute code %s in file %s on line %d',
160 1
                            $attributeCode,
161 1
                            $this->getFilename(),
0 ignored issues
show
Bug introduced by
It seems like getFilename() 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...
162 1
                            $this->getLineNumber()
0 ignored issues
show
Bug introduced by
It seems like getLineNumber() 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...
163 1
                        )
164 1
                    );
165 1
                }
166
167
                // stop processing
168 1
                continue;
169
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
170
            } else {
171
                // log a message in debug mode
172 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...
173 2
                    $this->getSystemLogger()->debug(
174 2
                        sprintf(
175 2
                            'Found attribute with attribute code %s in file %s on line %d',
176 2
                            $attributeCode,
177 2
                            $this->getFilename(),
0 ignored issues
show
Bug introduced by
It seems like getFilename() 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 2
                            $this->getLineNumber()
0 ignored issues
show
Bug introduced by
It seems like getLineNumber() 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 2
                        )
180 2
                    );
181 2
                }
182
            }
183
184
            // if yes, load the attribute by its code
185 5
            $attribute = $attributes[$attributeCode];
186
187
            // load the backend type => to find the apropriate entity
188 5
            $backendType = $attribute[MemberNames::BACKEND_TYPE];
189 5
            if ($backendType == null) {
190 1
                $this->getSystemLogger()->warning(
191 1
                    sprintf(
192 1
                        'Found EMTPY backend type for attribute %s in file %s on line %d',
193 1
                        $attributeCode,
194 1
                        $this->getFilename(),
0 ignored issues
show
Bug introduced by
It seems like getFilename() 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...
195 1
                        $this->getLineNumber()
0 ignored issues
show
Bug introduced by
It seems like getLineNumber() 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...
196 1
                    )
197 1
                );
198 1
                continue;
199
            }
200
201
            // do nothing on static backend type
202 4
            if ($backendType === BackendTypeKeys::BACKEND_TYPE_STATIC) {
203 1
                continue;
204
            }
205
206
            // query whether or not we've found a supported backend type
207 3
            if (isset($backendTypes[$backendType])) {
208
                // initialize attribute ID/code and backend type
209 2
                $this->attributeId = $attribute[MemberNames::ATTRIBUTE_ID];
210 2
                $this->attributeCode = $attributeCode;
211 2
                $this->backendType = $backendType;
212
213
                // initialize the persist method for the found backend type
214 2
                list ($persistMethod, ) = $backendTypes[$backendType];
215
216
                // set the attribute value
217 2
                $this->attributeValue = $attributeValue;
218
219
                // try to prepare the attribute values
220 2
                if ($attr = $this->prepareAttributes()) {
221
                    // initialize and persist the attribute
222 1
                    $entity = $this->initializeAttribute($attr);
223 1
                    $this->$persistMethod($entity);
224 1
                }
225
226
                // continue with the next value
227 2
                continue;
228
            }
229
230
            // log the debug message
231 1
            $this->getSystemLogger()->debug(
232 1
                sprintf(
233 1
                    'Found invalid backend type %s for attribute %s in file %s on line %s',
234 1
                    $backendType,
235 1
                    $attributeCode,
236 1
                    $this->getFilename(),
0 ignored issues
show
Bug introduced by
It seems like getFilename() 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...
237 1
                    $this->getLineNumber()
0 ignored issues
show
Bug introduced by
It seems like getLineNumber() 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...
238 1
                )
239 1
            );
240 7
        }
241 7
    }
242
243
    /**
244
     * Prepare the attributes of the entity that has to be persisted.
245
     *
246
     * @return array|null The prepared attributes
247
     */
248 2
    protected function prepareAttributes()
249
    {
250
251
        // laod the callbacks for the actual attribute code
252 2
        $callbacks = $this->getCallbacksByType($this->attributeCode);
253
254
        // invoke the pre-cast callbacks
255
        /** @var \TechDivision\Import\Callbacks\CallbackInterface $callback */
256 2
        foreach ($callbacks as $callback) {
257 1
            $this->attributeValue = $callback->handle($this);
258 2
        }
259
260
        // query whether or not the attribute has been be processed by the callbacks
261 2
        if ($this->attributeValue === null) {
262 1
            return;
263
        }
264
265
        // load the ID of the product that has been created recently
266 1
        $lastEntityId = $this->getPrimaryKey();
267
268
        // load the store ID, use the admin store if NO store view code has been set
269 1
        $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...
270
271
        // cast the value based on the backend type
272 1
        $castedValue = $this->castValueByBackendType($this->backendType, $this->attributeValue);
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...
273
274
        // prepare the attribute values
275 1
        return $this->initializeEntity(
0 ignored issues
show
Bug introduced by
It seems like initializeEntity() 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...
276
            array(
277 1
                MemberNames::ENTITY_ID    => $lastEntityId,
278 1
                MemberNames::ATTRIBUTE_ID => $this->attributeId,
279 1
                MemberNames::STORE_ID     => $storeId,
280 1
                MemberNames::VALUE        => $castedValue
281 1
            )
282 1
        );
283
    }
284
285
    /**
286
     * Initialize the category product with the passed attributes and returns an instance.
287
     *
288
     * @param array $attr The category product attributes
289
     *
290
     * @return array The initialized category product
291
     */
292 1
    protected function initializeAttribute(array $attr)
293
    {
294 1
        return $attr;
295
    }
296
297
    /**
298
     * Return's the PK to create the product => attribute relation.
299
     *
300
     * @return integer The PK to create the relation with
301
     */
302 1
    protected function getPrimaryKey()
303
    {
304 1
        return $this->getLastEntityId();
0 ignored issues
show
Bug introduced by
It seems like getLastEntityId() 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...
305
    }
306
307
    /**
308
     * Return's the array with callbacks for the passed type.
309
     *
310
     * @param string $type The type of the callbacks to return
311
     *
312
     * @return array The callbacks
313
     */
314 2
    protected function getCallbacksByType($type)
315
    {
316 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...
317
    }
318
319
    /**
320
     * Return's mapping for the supported backend types (for the product entity) => persist methods.
321
     *
322
     * @return array The mapping for the supported backend types
323
     */
324 7
    protected function getBackendTypes()
325
    {
326 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...
327
    }
328
329
    /**
330
     * Return's the attributes for the attribute set of the product that has to be created.
331
     *
332
     * @return array The attributes
333
     * @throws \Exception
334
     */
335 7
    protected function getAttributes()
336
    {
337 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...
338
    }
339
340
    /**
341
     * Return's the logger with the passed name, by default the system logger.
342
     *
343
     * @param string $name The name of the requested system logger
344
     *
345
     * @return \Psr\Log\LoggerInterface The logger instance
346
     * @throws \Exception Is thrown, if the requested logger is NOT available
347
     */
348
    abstract protected function getSystemLogger($name = LoggerKeys::SYSTEM);
349
350
    /**
351
     * Return's the column name that contains the primary key.
352
     *
353
     * @return string the column name that contains the primary key
354
     */
355
    abstract protected function getPrimaryKeyColumnName();
356
357
    /**
358
     * Queries whether or not the passed PK and store view code has already been processed.
359
     *
360
     * @param string $pk            The PK to check been processed
361
     * @param string $storeViewCode The store view code to check been processed
362
     *
363
     * @return boolean TRUE if the PK and store view code has been processed, else FALSE
364
     */
365
    abstract protected function storeViewHasBeenProcessed($pk, $storeViewCode);
366
}
367