Completed
Push — 16.x ( f0f2ba...764223 )
by Tim
02:12
created

AbstractObserver::getStateDetector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Observers\AbstractObserver
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\RowTrait;
24
use TechDivision\Import\Utils\ScopeKeys;
25
use TechDivision\Import\Utils\LoggerKeys;
26
use TechDivision\Import\Utils\EntityStatus;
27
use TechDivision\Import\Subjects\SubjectInterface;
28
use TechDivision\Import\Subjects\CleanUpColumnsSubjectInterface;
29
30
/**
31
 * An abstract observer implementation.
32
 *
33
 * @author    Tim Wagner <[email protected]>
34
 * @copyright 2016 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
abstract class AbstractObserver implements ObserverInterface
40
{
41
42
    /**
43
     * The trait that provides row handling functionality.
44
     *
45
     * @var TechDivision\Import\RowTrait
46
     */
47
    use RowTrait;
48
49
    /**
50
     * The obeserver's subject instance.
51
     *
52
     * @var \TechDivision\Import\Subjects\SubjectInterface
53
     */
54
    protected $subject;
55
56
    /**
57
     * The state detector instance.
58
     *
59
     * @var \TechDivision\Import\Observers\StateDetectorInterface
60
     */
61
    protected $stateDetector;
62
63
    /**
64
     * Initializes the observer with the state detector instance.
65
     *
66
     * @param \TechDivision\Import\Observers\StateDetectorInterface $stateDetector The state detector instance
67
     */
68 10
    public function __construct(StateDetectorInterface $stateDetector = null)
69
    {
70 10
        $this->stateDetector = $stateDetector;
71 10
    }
72
73
    /**
74
     * Set's the obeserver's subject instance to initialize the observer with.
75
     *
76
     * @param \TechDivision\Import\Subjects\SubjectInterface $subject The observer's subject
77
     *
78
     * @return void
79
     */
80 13
    protected function setSubject(SubjectInterface $subject)
81
    {
82 13
        $this->subject = $subject;
83 13
    }
84
85
    /**
86
     * Return's the observer's subject instance.
87
     *
88
     * @return object The observer's subject instance
89
     */
90 12
    public function getSubject()
91
    {
92 12
        return $this->subject;
93
    }
94
95
    /**
96
     * Return's the observer's state detector instance.
97
     *
98
     * @return \TechDivision\Import\Observers\StateDetectorInterface The state detector instance
99
     */
100 1
    protected function getStateDetector()
101
    {
102 1
        return $this->stateDetector;
103
    }
104
105
    /**
106
     * Initialize's and return's a new entity with the status 'create'.
107
     *
108
     * @param array $attr The attributes to merge into the new entity
109
     *
110
     * @return array The initialized entity
111
     */
112 3
    protected function initializeEntity(array $attr = array())
113
    {
114 3
        return array_merge($attr, array(EntityStatus::MEMBER_NAME => EntityStatus::STATUS_CREATE));
115
    }
116
117
    /**
118
     * Query whether or not the entity has to be processed.
119
     *
120
     * @param array $entity The entity to query for
121
     *
122
     * @return boolean TRUE if the entity has to be processed, else FALSE
123
     */
124 2
    protected function hasChanges(array $entity)
125
    {
126 2
        return in_array($entity[EntityStatus::MEMBER_NAME], array(EntityStatus::STATUS_CREATE, EntityStatus::STATUS_UPDATE));
127
    }
128
129
    /**
130
     * Detect's the entity state on the specific entity conditions and return's it.
131
     *
132
     * @param array       $entity        The entity loaded from the database
133
     * @param array       $attr          The entity data from the import file
134
     * @param string|null $changeSetName The change set name to use
135
     *
136
     * @return string The detected entity state
137
     */
138 1
    protected function detectState(array $entity, array $attr, $changeSetName = null)
139
    {
140 1
        return $this->getStateDetector() instanceof StateDetectorInterface ? $this->getStateDetector()->detect($this, $entity, $attr, $changeSetName) : EntityStatus::STATUS_UPDATE;
0 ignored issues
show
Unused Code introduced by
The call to StateDetectorInterface::detect() has too many arguments starting with $changeSetName.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
141
    }
142
143
    /**
144
     * Merge's and return's the entity with the passed attributes and set's the
145
     * passed status.
146
     *
147
     * @param array       $entity        The entity to merge the attributes into
148
     * @param array       $attr          The attributes to be merged
149
     * @param string|null $changeSetName The change set name to use
150
     *
151
     * @return array The merged entity
152
     * @todo https://github.com/techdivision/import/issues/179
153
     */
154 1
    protected function mergeEntity(array $entity, array $attr, $changeSetName = null)
155
    {
156
157
        // query whether or not the subject has columns that has to be cleaned-up
158 1
        if (($subject = $this->getSubject()) instanceof CleanUpColumnsSubjectInterface) {
159
            // load the columns that has to be cleaned-up
160
            $cleanUpColumns =  $subject->getCleanUpColumns();
161
            // load the column/member names from the attributes
162
            $columnNames = array_keys($attr);
163
164
            // iterate over the column names
165
            foreach ($columnNames as $columnName) {
166
                // we do NOT clean-up members that HAS a value or ARE in
167
                // the array with column names that has to be cleaned-up
168
                if ($this->hasValue($columnName) || in_array($columnName, $cleanUpColumns)) {
169
                    continue;
170
                }
171
                // unset the column, because it has NOT been cleaned-up
172
                unset($attr[$columnName]);
173
            }
174
        }
175
176
        // detect the state
177 1
        return array_merge($entity, $attr, array(EntityStatus::MEMBER_NAME => $this->detectState($entity, $attr, $changeSetName)));
178
    }
179
180
    /**
181
     * Merge's the passed status into the actual one.
182
     *
183
     * @param array $status The status to MergeBuilder
184
     *
185
     * @return void
186
     *
187
     * @codeCoverageIgnore
188
     */
189
    protected function mergeStatus(array $status)
190
    {
191
        $this->getSubject()->mergeStatus($status);
192
    }
193
194
    /**
195
     * Set's the array containing header row.
196
     *
197
     * @param array $headers The array with the header row
198
     *
199
     * @return void
200
     *
201
     * @codeCoverageIgnore
202
     */
203
    protected function setHeaders(array $headers)
204
    {
205
        $this->getSubject()->setHeaders($headers);
206
    }
207
208
    /**
209
     * Return's the array containing header row.
210
     *
211
     * @return array The array with the header row
212
     *
213
     * @codeCoverageIgnore
214
     */
215
    protected function getHeaders()
216
    {
217
        return $this->getSubject()->getHeaders();
218
    }
219
220
    /**
221
     * Return's the RegistryProcessor instance to handle the running threads.
222
     *
223
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
224
     *
225
     * @codeCoverageIgnore
226
     */
227
    protected function getRegistryProcessor()
228
    {
229
        return $this->getSubject()->getRegistryProcessor();
230
    }
231
232
    /**
233
     * Append's the exception suffix containing filename and line number to the
234
     * passed message. If no message has been passed, only the suffix will be
235
     * returned
236
     *
237
     * @param string|null $message    The message to append the exception suffix to
238
     * @param string|null $filename   The filename used to create the suffix
239
     * @param string|null $lineNumber The line number used to create the suffx
240
     *
241
     * @return string The message with the appended exception suffix
242
     *
243
     * @codeCoverageIgnore
244
     */
245
    protected function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
246
    {
247
        return $this->getSubject()->appendExceptionSuffix($message, $filename, $lineNumber);
248
    }
249
250
    /**
251
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
252
     * line number and column name and use it for a detailed exception message.
253
     *
254
     * @param string     $columnName The column name that should be resolved
255
     * @param \Exception $parent     The exception we want to wrap
256
     * @param string     $className  The class name of the exception type we want to wrap the parent one
257
     *
258
     * @return \Exception the wrapped exception
259
     *
260
     * @codeCoverageIgnore
261
     */
262
    protected function wrapException(
263
        $columnName,
264
        \Exception $parent = null,
265
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
266
    ) {
267
        return $this->getSubject()->wrapException($columnName, $parent, $className);
268
    }
269
270
    /**
271
     * Queries whether or not debug mode is enabled or not, default is TRUE.
272
     *
273
     * @return boolean TRUE if debug mode is enabled, else FALSE
274
     *
275
     * @codeCoverageIgnore
276
     */
277
    protected function isDebugMode()
278
    {
279
        return $this->getSubject()->isDebugMode();
280
    }
281
282
    /**
283
     * Stop's observer execution on the actual row.
284
     *
285
     * @return void
286
     *
287
     * @codeCoverageIgnore
288
     */
289
    protected function skipRow()
290
    {
291
        $this->getSubject()->skipRow();
292
    }
293
294
    /**
295
     * Return's the name of the file to import.
296
     *
297
     * @return string The filename
298
     *
299
     * @codeCoverageIgnore
300
     */
301
    protected function getFilename()
302
    {
303
        return $this->getSubject()->getFilename();
304
    }
305
306
    /**
307
     * Return's the actual line number.
308
     *
309
     * @return integer The line number
310
     *
311
     * @codeCoverageIgnore
312
     */
313
    protected function getLineNumber()
314
    {
315
        return $this->getSubject()->getLineNumber();
316
    }
317
318
    /**
319
     * Return's the logger with the passed name, by default the system logger.
320
     *
321
     * @param string $name The name of the requested system logger
322
     *
323
     * @return \Psr\Log\LoggerInterface The logger instance
324
     * @throws \Exception Is thrown, if the requested logger is NOT available
325
     *
326
     * @codeCoverageIgnore
327
     */
328
    protected function getSystemLogger($name = LoggerKeys::SYSTEM)
329
    {
330
        return $this->getSubject()->getSystemLogger($name);
331
    }
332
333
    /**
334
     * Return's the array with the system logger instances.
335
     *
336
     * @return array The logger instance
337
     *
338
     * @codeCoverageIgnore
339
     */
340
    protected function getSystemLoggers()
341
    {
342
        return $this->getSubject()->getSystemLoggers();
343
    }
344
345
    /**
346
     * Return's the multiple field delimiter character to use, default value is comma (,).
347
     *
348
     * @return string The multiple field delimiter character
349
     *
350
     * @codeCoverageIgnore
351
     */
352
    protected function getMultipleFieldDelimiter()
353
    {
354
        return $this->getSubject()->getMultipleFieldDelimiter();
355
    }
356
357
    /**
358
     * Return's the multiple value delimiter character to use, default value is comma (|).
359
     *
360
     * @return string The multiple value delimiter character
361
     *
362
     * @codeCoverageIgnore
363
     */
364
    protected function getMultipleValueDelimiter()
365
    {
366
        return $this->getSubject()->getMultipleValueDelimiter();
367
    }
368
369
    /**
370
     * Queries whether or not the header with the passed name is available.
371
     *
372
     * @param string $name The header name to query
373
     *
374
     * @return boolean TRUE if the header is available, else FALSE
375
     *
376
     * @codeCoverageIgnore
377
     */
378
    public function hasHeader($name)
379
    {
380
        return $this->getSubject()->hasHeader($name);
381
    }
382
383
    /**
384
     * Return's the header value for the passed name.
385
     *
386
     * @param string $name The name of the header to return the value for
387
     *
388
     * @return mixed The header value
389
     * @throws \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
390
     *
391
     * @codeCoverageIgnore
392
     */
393
    protected function getHeader($name)
394
    {
395
        return $this->getSubject()->getHeader($name);
396
    }
397
398
    /**
399
     * Add's the header with the passed name and position, if not NULL.
400
     *
401
     * @param string $name The header name to add
402
     *
403
     * @return integer The new headers position
404
     *
405
     * @codeCoverageIgnore
406
     */
407
    protected function addHeader($name)
408
    {
409
        return $this->getSubject()->addHeader($name);
410
    }
411
412
    /**
413
     * Return's the ID of the product that has been created recently.
414
     *
415
     * @return string The entity Id
416
     *
417
     * @codeCoverageIgnore
418
     */
419
    protected function getLastEntityId()
420
    {
421
        return $this->getSubject()->getLastEntityId();
0 ignored issues
show
Bug introduced by
The method getLastEntityId() does not seem to exist on object<TechDivision\Impo...jects\SubjectInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
422
    }
423
424
    /**
425
     * Return's the source date format to use.
426
     *
427
     * @return string The source date format
428
     *
429
     * @codeCoverageIgnore
430
     */
431
    protected function getSourceDateFormat()
432
    {
433
        return $this->getSubject()->getSourceDateFormat();
434
    }
435
436
    /**
437
     * Cast's the passed value based on the backend type information.
438
     *
439
     * @param string $backendType The backend type to cast to
440
     * @param mixed  $value       The value to be casted
441
     *
442
     * @return mixed The casted value
443
     *
444
     * @codeCoverageIgnore
445
     */
446
    public function castValueByBackendType($backendType, $value)
447
    {
448
        return $this->getSubject()->castValueByBackendType($backendType, $value);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method castValueByBackendType() does only exist in the following implementations of said interface: TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\ValidatorSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
449
    }
450
451
    /**
452
     * Set's the store view code the create the product/attributes for.
453
     *
454
     * @param string $storeViewCode The store view code
455
     *
456
     * @return void
457
     *
458
     * @codeCoverageIgnore
459
     */
460
    protected function setStoreViewCode($storeViewCode)
461
    {
462
        $this->getSubject()->setStoreViewCode($storeViewCode);
463
    }
464
465
    /**
466
     * Return's the store view code the create the product/attributes for.
467
     *
468
     * @param string|null $default The default value to return, if the store view code has not been set
469
     *
470
     * @return string The store view code
471
     *
472
     * @codeCoverageIgnore
473
     */
474
    protected function getStoreViewCode($default = null)
475
    {
476
        return $this->getSubject()->getStoreViewCode($default);
477
    }
478
479
    /**
480
     * Prepare's the store view code in the subject.
481
     *
482
     * @return void
483
     *
484
     * @codeCoverageIgnore
485
     */
486
    protected function prepareStoreViewCode()
487
    {
488
        $this->getSubject()->prepareStoreViewCode();
489
    }
490
491
    /**
492
     * Return's the store ID of the store with the passed store view code
493
     *
494
     * @param string $storeViewCode The store view code to return the store ID for
495
     *
496
     * @return integer The ID of the store with the passed ID
497
     * @throws \Exception Is thrown, if the store with the actual code is not available
498
     *
499
     * @codeCoverageIgnore
500
     */
501
    protected function getStoreId($storeViewCode)
502
    {
503
        return $this->getSubject()->getStoreId($storeViewCode);
504
    }
505
506
    /**
507
     * Return's the store ID of the actual row, or of the default store
508
     * if no store view code is set in the CSV file.
509
     *
510
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
511
     *
512
     * @return integer The ID of the actual store
513
     * @throws \Exception Is thrown, if the store with the actual code is not available
514
     *
515
     * @codeCoverageIgnore
516
     */
517
    protected function getRowStoreId($default = null)
518
    {
519
        return $this->getSubject()->getRowStoreId($default);
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
520
    }
521
522
    /**
523
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
524
     * If the passed value is NOT a valid date, NULL will be returned.
525
     *
526
     * @param string|null $value The value to format
527
     *
528
     * @return string The formatted date
529
     *
530
     * @codeCoverageIgnore
531
     */
532
    protected function formatDate($value)
533
    {
534
        return $this->getSubject()->formatDate($value);
535
    }
536
537
    /**
538
     * Extracts the elements of the passed value by exploding them
539
     * with the also passed delimiter.
540
     *
541
     * @param string      $value     The value to extract
542
     * @param string|null $delimiter The delimiter used to extrace the elements
543
     *
544
     * @return array The exploded values
545
     *
546
     * @codeCoverageIgnore
547
     */
548
    protected function explode($value, $delimiter = null)
549
    {
550
        return $this->getSubject()->explode($value, $delimiter);
551
    }
552
553
    /**
554
     * Return's the Magento configuration value.
555
     *
556
     * @param string  $path    The Magento path of the requested configuration value
557
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
558
     * @param string  $scope   The scope the configuration value has been set
559
     * @param integer $scopeId The scope ID the configuration value has been set
560
     *
561
     * @return mixed The configuration value
562
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
563
     *
564
     * @codeCoverageIgnore
565
     */
566
    protected function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
567
    {
568
        return $this->getSubject()->getCoreConfigData($path, $default, $scope, $scopeId);
569
    }
570
}
571