Completed
Pull Request — master (#18)
by Tim
02:51
created

AbstractSubject::import()   C

Complexity

Conditions 7
Paths 33

Size

Total Lines 89
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 89
c 0
b 0
f 0
ccs 0
cts 44
cp 0
rs 6.5134
cc 7
eloc 36
nc 33
nop 2
crap 56

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
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\Subjects;
22
23
use Psr\Log\LoggerInterface;
24
use League\Flysystem\Filesystem;
25
use League\Flysystem\Adapter\Local;
26
use League\Flysystem\FilesystemInterface;
27
use Goodby\CSV\Import\Standard\Lexer;
28
use Goodby\CSV\Import\Standard\LexerConfig;
29
use Goodby\CSV\Import\Standard\Interpreter;
30
use TechDivision\Import\Utils\ConfigurationKeys;
31
use TechDivision\Import\Services\RegistryProcessor;
32
use TechDivision\Import\Callbacks\CallbackInterface;
33
use TechDivision\Import\Observers\ObserverInterface;
34
use TechDivision\Import\Services\RegistryProcessorInterface;
35
use TechDivision\Import\Configuration\SubjectInterface as SubjectConfigurationInterface;
36
use TechDivision\Import\Utils\RegistryKeys;
37
38
/**
39
 * An abstract subject implementation.
40
 *
41
 * @author    Tim Wagner <[email protected]>
42
 * @copyright 2016 TechDivision GmbH <[email protected]>
43
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
44
 * @link      https://github.com/techdivision/import
45
 * @link      http://www.techdivision.com
46
 */
47
abstract class AbstractSubject implements SubjectInterface
48
{
49
50
    /**
51
     * The root directory for the virtual filesystem.
52
     *
53
     * @var string
54
     */
55
    protected $rootDir;
56
57
    /**
58
     * The system configuration.
59
     *
60
     * @var \TechDivision\Import\Configuration\SubjectInterface
61
     */
62
    protected $configuration;
63
64
    /**
65
     * The system logger implementation.
66
     *
67
     * @var \Psr\Log\LoggerInterface
68
     */
69
    protected $systemLogger;
70
71
    /**
72
     * The RegistryProcessor instance to handle running threads.
73
     *
74
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
75
     */
76
    protected $registryProcessor;
77
78
    /**
79
     * The actions unique serial.
80
     *
81
     * @var string
82
     */
83
    protected $serial;
84
85
    /**
86
     * The name of the file to be imported.
87
     *
88
     * @var string
89
     */
90
    protected $filename;
91
92
    /**
93
     * Array with the subject's observers.
94
     *
95
     * @var array
96
     */
97
    protected $observers = array();
98
99
    /**
100
     * Array with the subject's callbacks.
101
     *
102
     * @var array
103
     */
104
    protected $callbacks = array();
105
106
    /**
107
     * Contain's the column names from the header line.
108
     *
109
     * @var array
110
     */
111
    protected $headers = array();
112
113
    /**
114
     * The virtual filesystem instance.
115
     *
116
     * @var \League\Flysystem\FilesystemInterface
117
     */
118
    protected $filesystem;
119
120
    /**
121
     * Set's the array containing header row.
122
     *
123
     * @param array $headers The array with the header row
124
     *
125
     * @return void
126
     */
127
    public function setHeaders(array $headers)
128
    {
129
        $this->headers = $headers;
130
    }
131
132
    /**
133
     * Return's the array containing header row.
134
     *
135
     * @return array The array with the header row
136
     */
137
    public function getHeaders()
138
    {
139
        return $this->headers;
140
    }
141
142
    /**
143
     * Queries whether or not the header with the passed name is available.
144
     *
145
     * @param string $name The header name to query
146
     *
147
     * @return boolean TRUE if the header is available, else FALSE
148
     */
149
    public function hasHeader($name)
150
    {
151
        return isset($this->headers[$name]);
152
    }
153
154
    /**
155
     * Return's the header value for the passed name.
156
     *
157
     * @param string $name The name of the header to return the value for
158
     *
159
     * @return mixed The header value
160
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
161
     */
162
    public function getHeader($name)
163
    {
164
165
        // query whether or not, the header is available
166
        if (isset($this->headers[$name])) {
167
            return $this->headers[$name];
168
        }
169
170
        // throw an exception, if not
171
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
172
    }
173
174
    /**
175
     * Add's the header with the passed name and position, if not NULL.
176
     *
177
     * @param string $name The header name to add
178
     *
179
     * @return integer The new headers position
180
     */
181
    public function addHeader($name)
182
    {
183
184
        // add the header
185
        $this->headers[$name] = $position = sizeof($this->headers);
186
187
        // return the new header's position
188
        return $position;
189
    }
190
191
    /**
192
     * Set's the system configuration.
193
     *
194
     * @param \TechDivision\Import\Configuration\Subject $configuration The system configuration
195
     *
196
     * @return void
197
     */
198
    public function setConfiguration(SubjectConfigurationInterface $configuration)
199
    {
200
        $this->configuration = $configuration;
201
    }
202
203
    /**
204
     * Return's the system configuration.
205
     *
206
     * @return \TechDivision\Import\Configuration\SubjectInterface The system configuration
207
     */
208
    public function getConfiguration()
209
    {
210
        return $this->configuration;
211
    }
212
213
    /**
214
     * Set's the system logger.
215
     *
216
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
217
     *
218
     * @return void
219
     */
220
    public function setSystemLogger(LoggerInterface $systemLogger)
221
    {
222
        $this->systemLogger = $systemLogger;
223
    }
224
225
    /**
226
     * Return's the system logger.
227
     *
228
     * @return \Psr\Log\LoggerInterface The system logger instance
229
     */
230
    public function getSystemLogger()
231
    {
232
        return $this->systemLogger;
233
    }
234
235
    /**
236
     * Set's root directory for the virtual filesystem.
237
     *
238
     * @param string $rootDir The root directory for the virtual filesystem
239
     *
240
     * @return void
241
     */
242
    public function setRootDir($rootDir)
243
    {
244
        $this->rootDir = $rootDir;
245
    }
246
247
    /**
248
     * Return's the root directory for the virtual filesystem.
249
     *
250
     * @return string The root directory for the virtual filesystem
251
     */
252
    public function getRootDir()
253
    {
254
        return $this->rootDir;
255
    }
256
257
    /**
258
     * Set's the virtual filesystem instance.
259
     *
260
     * @param \League\Flysystem\FilesystemInterface $filesystem The filesystem instance
261
     *
262
     * @return void
263
     */
264
    public function setFilesystem(FilesystemInterface $filesystem)
265
    {
266
        $this->filesystem = $filesystem;
267
    }
268
269
    /**
270
     * Return's the virtual filesystem instance.
271
     *
272
     * @return \League\Flysystem\FilesystemInterface The filesystem instance
273
     */
274
    public function getFilesystem()
275
    {
276
        return $this->filesystem;
277
    }
278
279
    /**
280
     * Sets's the RegistryProcessor instance to handle the running threads.
281
     *
282
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
283
     *
284
     * @return void
285
     */
286
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
287
    {
288
        $this->registryProcessor = $registryProcessor;
289
    }
290
291
    /**
292
     * Return's the RegistryProcessor instance to handle the running threads.
293
     *
294
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
295
     */
296
    public function getRegistryProcessor()
297
    {
298
        return $this->registryProcessor;
299
    }
300
301
    /**
302
     * Set's the unique serial for this import process.
303
     *
304
     * @param string $serial The unique serial
305
     *
306
     * @return void
307
     */
308
    public function setSerial($serial)
309
    {
310
        $this->serial = $serial;
311
    }
312
313
    /**
314
     * Return's the unique serial for this import process.
315
     *
316
     * @return string The unique serial
317
     */
318
    public function getSerial()
319
    {
320
        return $this->serial;
321
    }
322
323
    /**
324
     * Set's the name of the file to import
325
     *
326
     * @param string $filename The filename
327
     *
328
     * @return void
329
     */
330
    public function setFilename($filename)
331
    {
332
        $this->filename = $filename;
333
    }
334
335
    /**
336
     * Return's the name of the file to import.
337
     *
338
     * @return string The filename
339
     */
340
    public function getFilename()
341
    {
342
        return $this->filename;
343
    }
344
345
    /**
346
     * Return's the source date format to use.
347
     *
348
     * @return string The source date format
349
     */
350
    public function getSourceDateFormat()
351
    {
352
        return $this->getConfiguration()->getSourceDateFormat();
353
    }
354
355
    /**
356
     * Return's the multiple field delimiter character to use, default value is comma (,).
357
     *
358
     * @return string The multiple field delimiter character
359
     */
360
    public function getMultipleFieldDelimiter()
361
    {
362
        return $this->getConfiguration()->getMultipleFieldDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleFieldDelimiter() does not seem to exist on object<TechDivision\Impo...ation\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...
363
    }
364
365
    /**
366
     * Return's the initialized PDO connection.
367
     *
368
     * @return \PDO The initialized PDO connection
369
     */
370
    public function getConnection()
371
    {
372
        return $this->getProductProcessor()->getConnection();
0 ignored issues
show
Bug introduced by
The method getProductProcessor() does not seem to exist on object<TechDivision\Impo...bjects\AbstractSubject>.

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...
373
    }
374
375
    /**
376
     * Intializes the previously loaded global data for exactly one bunch.
377
     *
378
     * @return void
379
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
380
     */
381
    public function setUp()
382
    {
383
384
        // initialize the filesystems root directory
385
        $this->setRootDir(
386
            $this->getConfiguration()->getParam(ConfigurationKeys::ROOT_DIRECTORY, getcwd())
387
        );
388
389
        // initialize the filesystem
390
        $this->setFilesystem(new Filesystem(new Local($this->getRootDir())));
391
    }
392
393
    /**
394
     * This method tries to resolve the passed path and returns it. If the path
395
     * is relative, the actual working directory will be prepended.
396
     *
397
     * @param string $path The path to be resolved
398
     *
399
     * @return string The resolved path
400
     * @throws \InvalidArgumentException Is thrown, if the path can not be resolved
401
     */
402
    public function resolvePath($path)
403
    {
404
        // if we've an absolute path, return it immediately
405
        if ($this->getFilesystem()->has($path)) {
406
            return $path;
407
        }
408
409
        // try to prepend the actual working directory, assuming we've a relative path
410
        if ($this->getFilesystem()->has($path = getcwd() . DIRECTORY_SEPARATOR . $path)) {
411
            return $path;
412
        }
413
414
        // throw an exception if the passed directory doesn't exists
415
        throw new \InvalidArgumentException(
416
            sprintf('Directory %s doesn\'t exist', $path)
417
        );
418
    }
419
420
    /**
421
     * Clean up the global data after importing the variants.
422
     *
423
     * @return void
424
     */
425
    public function tearDown()
426
    {
427
428
        // load the registry processor
429
        $registryProcessor = $this->getRegistryProcessor();
430
431
        // update the source directory for the next subject
432
        $registryProcessor->mergeAttributesRecursive(
433
            $this->getSerial(),
434
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir())
435
        );
436
437
        // log a debug message with the new source directory
438
        $this->getSystemLogger()->debug(
439
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir())
440
        );
441
    }
442
443
    /**
444
     * Return's the next source directory, which will be the target directory
445
     * of this subject, in most cases.
446
     *
447
     * @return string The new source directory
448
     */
449
    protected function getNewSourceDir()
450
    {
451
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial());
452
    }
453
454
    /**
455
     * Register the passed observer with the specific type.
456
     *
457
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
458
     * @param string                                           $type     The type to register the observer with
459
     *
460
     * @return void
461
     */
462
    public function registerObserver(ObserverInterface $observer, $type)
463
    {
464
465
        // query whether or not the array with the callbacks for the
466
        // passed type has already been initialized, or not
467
        if (!isset($this->observers[$type])) {
468
            $this->observers[$type] = array();
469
        }
470
471
        // append the callback with the instance of the passed type
472
        $this->observers[$type][] = $observer;
473
    }
474
475
    /**
476
     * Register the passed callback with the specific type.
477
     *
478
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
479
     * @param string                                           $type     The type to register the callback with
480
     *
481
     * @return void
482
     */
483
    public function registerCallback(CallbackInterface $callback, $type)
484
    {
485
486
        // query whether or not the array with the callbacks for the
487
        // passed type has already been initialized, or not
488
        if (!isset($this->callbacks[$type])) {
489
            $this->callbacks[$type] = array();
490
        }
491
492
        // append the callback with the instance of the passed type
493
        $this->callbacks[$type][] = $callback;
494
    }
495
496
    /**
497
     * Return's the array with callbacks for the passed type.
498
     *
499
     * @param string $type The type of the callbacks to return
500
     *
501
     * @return array The callbacks
502
     */
503
    public function getCallbacksByType($type)
504
    {
505
506
        // initialize the array for the callbacks
507
        $callbacks = array();
508
509
        // query whether or not callbacks for the type are available
510
        if (isset($this->callbacks[$type])) {
511
            $callbacks = $this->callbacks[$type];
512
        }
513
514
        // return the array with the type's callbacks
515
        return $callbacks;
516
    }
517
518
    /**
519
     * Return's the array with the available observers.
520
     *
521
     * @return array The observers
522
     */
523
    public function getObservers()
524
    {
525
        return $this->observers;
526
    }
527
528
    /**
529
     * Return's the array with the available callbacks.
530
     *
531
     * @return array The callbacks
532
     */
533
    public function getCallbacks()
534
    {
535
        return $this->callbacks;
536
    }
537
538
    /**
539
     * Imports the content of the file with the passed filename.
540
     *
541
     * @param string $serial   The unique process serial
542
     * @param string $filename The filename to process
543
     *
544
     * @return void
545
     * @throws \Exception Is thrown, if the import can't be processed
546
     */
547
    public function import($serial, $filename)
548
    {
549
550
        try {
551
            // prepare the pattern to query whether the file has to be processed or not
552
            $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
553
554
            // stop processing, if the filename doesn't match
555
            if (!preg_match($pattern, $filename)) {
556
                return;
557
            }
558
559
            // prepare the flag filenames
560
            $inProgressFilename = sprintf('%s.inProgress', $filename);
561
            $importedFilename = sprintf('%s.imported', $filename);
562
            $failedFilename = sprintf('%s.failed', $filename);
563
564
            // query whether or not the file has already been imported
565
            if (is_file($failedFilename) ||
566
                is_file($importedFilename) ||
567
                is_file($inProgressFilename)
568
            ) {
569
                // log a debug message
570
                $systemLogger->debug(
0 ignored issues
show
Bug introduced by
The variable $systemLogger seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
571
                    sprintf('Import running, found inProgress file %s', $inProgressFilename)
572
                );
573
574
                // ignore the file
575
                continue;
576
            }
577
578
            // flag file as in progress
579
            touch($inProgressFilename);
580
581
            // track the start time
582
            $startTime = microtime(true);
583
584
            // initialize serial and filename
585
            $this->setSerial($serial);
586
            $this->setFilename($filename);
587
588
            // load the system logger
589
            $systemLogger = $this->getSystemLogger();
590
591
            // initialize the global global data to import a bunch
592
            $this->setUp();
593
594
            // initialize the lexer instance itself
595
            $lexer = new Lexer($this->getLexerConfig());
596
597
            // initialize the interpreter
598
            $interpreter = new Interpreter();
599
            $interpreter->addObserver(array($this, 'importRow'));
600
601
            // query whether or not we want to use the strict mode
602
            if (!$this->getConfiguration()->isStrictMode()) {
603
                $interpreter->unstrict();
604
            }
605
606
            // log a message that the file has to be imported
607
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
608
609
            // parse the CSV file to be imported
610
            $lexer->parse($filename, $interpreter);
611
612
            // track the time needed for the import in seconds
613
            $endTime = microtime(true) - $startTime;
614
615
            // clean up the data after importing the bunch
616
            $this->tearDown();
617
618
            // log a message that the file has successfully been imported
619
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
620
621
            // rename flag file, because import has been successfull
622
            rename($inProgressFilename, $importedFilename);
623
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
624
        } catch (\Exception $e) {
625
            // rename the flag file, because import failed and write the stack trace
626
            rename($inProgressFilename, $failedFilename);
0 ignored issues
show
Bug introduced by
The variable $inProgressFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $failedFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
627
            file_put_contents($failedFilename, $e->__toString());
628
629
            // clean up the data after importing the bunch
630
            $this->tearDown();
631
632
            // re-throw the exception
633
            throw $e;
634
        }
635
    }
636
637
    /**
638
     * Initialize and return the lexer configuration.
639
     *
640
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
641
     */
642
    protected function getLexerConfig()
643
    {
644
645
        // initialize the lexer configuration
646
        $config = new LexerConfig();
647
648
        // query whether or not a delimiter character has been configured
649
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
650
            $config->setDelimiter($delimiter);
651
        }
652
653
        // query whether or not a custom escape character has been configured
654
        if ($escape = $this->getConfiguration()->getEscape()) {
655
            $config->setEscape($escape);
656
        }
657
658
        // query whether or not a custom enclosure character has been configured
659
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
660
            $config->setEnclosure($enclosure);
661
        }
662
663
        // query whether or not a custom source charset has been configured
664
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
665
            $config->setFromCharset($fromCharset);
666
        }
667
668
        // query whether or not a custom target charset has been configured
669
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
670
            $config->setToCharset($toCharset);
671
        }
672
673
        // return the lexer configuratio
674
        return $config;
675
    }
676
677
    /**
678
     * Imports the passed row into the database.
679
     *
680
     * If the import failed, the exception will be catched and logged,
681
     * but the import process will be continued.
682
     *
683
     * @param array $row The row with the data to be imported
684
     *
685
     * @return void
686
     */
687
    public function importRow(array $row)
688
    {
689
690
        // initialize the headers with the columns from the first line
691
        if (sizeof($this->getHeaders()) === 0) {
692
            $this->setHeaders(array_flip($row));
693
            return;
694
        }
695
696
        // process the observers
697
        foreach ($this->getObservers() as $observers) {
698
            // invoke the pre-import/import and post-import observers
699
            foreach ($observers as $observer) {
700
                if ($observer instanceof ObserverInterface) {
701
                    $row = $observer->handle($row);
0 ignored issues
show
Bug introduced by
The method handle() does not seem to exist on object<TechDivision\Impo...vers\ObserverInterface>.

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...
702
                }
703
            }
704
        }
705
    }
706
}
707