Completed
Push — master ( 93fc6e...357097 )
by Tim
9s
created

AbstractSubject::setHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
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
     * The actual line number.
122
     *
123
     * @var integer
124
     */
125
    protected $lineNumber = 0;
126
127
    /**
128
     * Return's the actual line number.
129
     *
130
     * @return integer The line number
131
     */
132
    public function getLineNumber()
133
    {
134
        return $this->lineNumber;
135
    }
136
137
    /**
138
     * Set's the array containing header row.
139
     *
140
     * @param array $headers The array with the header row
141
     *
142
     * @return void
143
     */
144
    public function setHeaders(array $headers)
145
    {
146
        $this->headers = $headers;
147
    }
148
149
    /**
150
     * Return's the array containing header row.
151
     *
152
     * @return array The array with the header row
153
     */
154
    public function getHeaders()
155
    {
156
        return $this->headers;
157
    }
158
159
    /**
160
     * Queries whether or not the header with the passed name is available.
161
     *
162
     * @param string $name The header name to query
163
     *
164
     * @return boolean TRUE if the header is available, else FALSE
165
     */
166
    public function hasHeader($name)
167
    {
168
        return isset($this->headers[$name]);
169
    }
170
171
    /**
172
     * Return's the header value for the passed name.
173
     *
174
     * @param string $name The name of the header to return the value for
175
     *
176
     * @return mixed The header value
177
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
178
     */
179
    public function getHeader($name)
180
    {
181
182
        // query whether or not, the header is available
183
        if (isset($this->headers[$name])) {
184
            return $this->headers[$name];
185
        }
186
187
        // throw an exception, if not
188
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
189
    }
190
191
    /**
192
     * Add's the header with the passed name and position, if not NULL.
193
     *
194
     * @param string $name The header name to add
195
     *
196
     * @return integer The new headers position
197
     */
198
    public function addHeader($name)
199
    {
200
201
        // add the header
202
        $this->headers[$name] = $position = sizeof($this->headers);
203
204
        // return the new header's position
205
        return $position;
206
    }
207
208
    /**
209
     * Set's the system configuration.
210
     *
211
     * @param \TechDivision\Import\Configuration\Subject $configuration The system configuration
212
     *
213
     * @return void
214
     */
215
    public function setConfiguration(SubjectConfigurationInterface $configuration)
216
    {
217
        $this->configuration = $configuration;
218
    }
219
220
    /**
221
     * Return's the system configuration.
222
     *
223
     * @return \TechDivision\Import\Configuration\SubjectInterface The system configuration
224
     */
225
    public function getConfiguration()
226
    {
227
        return $this->configuration;
228
    }
229
230
    /**
231
     * Set's the system logger.
232
     *
233
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
234
     *
235
     * @return void
236
     */
237
    public function setSystemLogger(LoggerInterface $systemLogger)
238
    {
239
        $this->systemLogger = $systemLogger;
240
    }
241
242
    /**
243
     * Return's the system logger.
244
     *
245
     * @return \Psr\Log\LoggerInterface The system logger instance
246
     */
247
    public function getSystemLogger()
248
    {
249
        return $this->systemLogger;
250
    }
251
252
    /**
253
     * Set's root directory for the virtual filesystem.
254
     *
255
     * @param string $rootDir The root directory for the virtual filesystem
256
     *
257
     * @return void
258
     */
259
    public function setRootDir($rootDir)
260
    {
261
        $this->rootDir = $rootDir;
262
    }
263
264
    /**
265
     * Return's the root directory for the virtual filesystem.
266
     *
267
     * @return string The root directory for the virtual filesystem
268
     */
269
    public function getRootDir()
270
    {
271
        return $this->rootDir;
272
    }
273
274
    /**
275
     * Set's the virtual filesystem instance.
276
     *
277
     * @param \League\Flysystem\FilesystemInterface $filesystem The filesystem instance
278
     *
279
     * @return void
280
     */
281
    public function setFilesystem(FilesystemInterface $filesystem)
282
    {
283
        $this->filesystem = $filesystem;
284
    }
285
286
    /**
287
     * Return's the virtual filesystem instance.
288
     *
289
     * @return \League\Flysystem\FilesystemInterface The filesystem instance
290
     */
291
    public function getFilesystem()
292
    {
293
        return $this->filesystem;
294
    }
295
296
    /**
297
     * Sets's the RegistryProcessor instance to handle the running threads.
298
     *
299
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
300
     *
301
     * @return void
302
     */
303
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
304
    {
305
        $this->registryProcessor = $registryProcessor;
306
    }
307
308
    /**
309
     * Return's the RegistryProcessor instance to handle the running threads.
310
     *
311
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
312
     */
313
    public function getRegistryProcessor()
314
    {
315
        return $this->registryProcessor;
316
    }
317
318
    /**
319
     * Set's the unique serial for this import process.
320
     *
321
     * @param string $serial The unique serial
322
     *
323
     * @return void
324
     */
325
    public function setSerial($serial)
326
    {
327
        $this->serial = $serial;
328
    }
329
330
    /**
331
     * Return's the unique serial for this import process.
332
     *
333
     * @return string The unique serial
334
     */
335
    public function getSerial()
336
    {
337
        return $this->serial;
338
    }
339
340
    /**
341
     * Set's the name of the file to import
342
     *
343
     * @param string $filename The filename
344
     *
345
     * @return void
346
     */
347
    public function setFilename($filename)
348
    {
349
        $this->filename = $filename;
350
    }
351
352
    /**
353
     * Return's the name of the file to import.
354
     *
355
     * @return string The filename
356
     */
357
    public function getFilename()
358
    {
359
        return $this->filename;
360
    }
361
362
    /**
363
     * Return's the source date format to use.
364
     *
365
     * @return string The source date format
366
     */
367
    public function getSourceDateFormat()
368
    {
369
        return $this->getConfiguration()->getSourceDateFormat();
370
    }
371
372
    /**
373
     * Return's the multiple field delimiter character to use, default value is comma (,).
374
     *
375
     * @return string The multiple field delimiter character
376
     */
377
    public function getMultipleFieldDelimiter()
378
    {
379
        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...
380
    }
381
382
    /**
383
     * Return's the initialized PDO connection.
384
     *
385
     * @return \PDO The initialized PDO connection
386
     */
387
    public function getConnection()
388
    {
389
        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...
390
    }
391
392
    /**
393
     * Intializes the previously loaded global data for exactly one bunch.
394
     *
395
     * @return void
396
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
397
     */
398
    public function setUp()
399
    {
400
401
        // initialize the filesystems root directory
402
        $this->setRootDir(
403
            $this->getConfiguration()->getParam(ConfigurationKeys::ROOT_DIRECTORY, getcwd())
404
        );
405
406
        // initialize the filesystem
407
        $this->setFilesystem(new Filesystem(new Local($this->getRootDir())));
408
    }
409
410
    /**
411
     * This method tries to resolve the passed path and returns it. If the path
412
     * is relative, the actual working directory will be prepended.
413
     *
414
     * @param string $path The path to be resolved
415
     *
416
     * @return string The resolved path
417
     * @throws \InvalidArgumentException Is thrown, if the path can not be resolved
418
     */
419
    public function resolvePath($path)
420
    {
421
        // if we've an absolute path, return it immediately
422
        if ($this->getFilesystem()->has($path)) {
423
            return $path;
424
        }
425
426
        // try to prepend the actual working directory, assuming we've a relative path
427
        if ($this->getFilesystem()->has($path = getcwd() . DIRECTORY_SEPARATOR . $path)) {
428
            return $path;
429
        }
430
431
        // throw an exception if the passed directory doesn't exists
432
        throw new \InvalidArgumentException(
433
            sprintf('Directory %s doesn\'t exist', $path)
434
        );
435
    }
436
437
    /**
438
     * Clean up the global data after importing the variants.
439
     *
440
     * @return void
441
     */
442
    public function tearDown()
443
    {
444
445
        // load the registry processor
446
        $registryProcessor = $this->getRegistryProcessor();
447
448
        // update the source directory for the next subject
449
        $registryProcessor->mergeAttributesRecursive(
450
            $this->getSerial(),
451
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir())
452
        );
453
454
        // log a debug message with the new source directory
455
        $this->getSystemLogger()->debug(
456
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir())
457
        );
458
    }
459
460
    /**
461
     * Return's the next source directory, which will be the target directory
462
     * of this subject, in most cases.
463
     *
464
     * @return string The new source directory
465
     */
466
    protected function getNewSourceDir()
467
    {
468
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial());
469
    }
470
471
    /**
472
     * Register the passed observer with the specific type.
473
     *
474
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
475
     * @param string                                           $type     The type to register the observer with
476
     *
477
     * @return void
478
     */
479
    public function registerObserver(ObserverInterface $observer, $type)
480
    {
481
482
        // query whether or not the array with the callbacks for the
483
        // passed type has already been initialized, or not
484
        if (!isset($this->observers[$type])) {
485
            $this->observers[$type] = array();
486
        }
487
488
        // append the callback with the instance of the passed type
489
        $this->observers[$type][] = $observer;
490
    }
491
492
    /**
493
     * Register the passed callback with the specific type.
494
     *
495
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
496
     * @param string                                           $type     The type to register the callback with
497
     *
498
     * @return void
499
     */
500
    public function registerCallback(CallbackInterface $callback, $type)
501
    {
502
503
        // query whether or not the array with the callbacks for the
504
        // passed type has already been initialized, or not
505
        if (!isset($this->callbacks[$type])) {
506
            $this->callbacks[$type] = array();
507
        }
508
509
        // append the callback with the instance of the passed type
510
        $this->callbacks[$type][] = $callback;
511
    }
512
513
    /**
514
     * Return's the array with callbacks for the passed type.
515
     *
516
     * @param string $type The type of the callbacks to return
517
     *
518
     * @return array The callbacks
519
     */
520
    public function getCallbacksByType($type)
521
    {
522
523
        // initialize the array for the callbacks
524
        $callbacks = array();
525
526
        // query whether or not callbacks for the type are available
527
        if (isset($this->callbacks[$type])) {
528
            $callbacks = $this->callbacks[$type];
529
        }
530
531
        // return the array with the type's callbacks
532
        return $callbacks;
533
    }
534
535
    /**
536
     * Return's the array with the available observers.
537
     *
538
     * @return array The observers
539
     */
540
    public function getObservers()
541
    {
542
        return $this->observers;
543
    }
544
545
    /**
546
     * Return's the array with the available callbacks.
547
     *
548
     * @return array The callbacks
549
     */
550
    public function getCallbacks()
551
    {
552
        return $this->callbacks;
553
    }
554
555
    /**
556
     * Imports the content of the file with the passed filename.
557
     *
558
     * @param string $serial   The unique process serial
559
     * @param string $filename The filename to process
560
     *
561
     * @return void
562
     * @throws \Exception Is thrown, if the import can't be processed
563
     */
564
    public function import($serial, $filename)
565
    {
566
567
        try {
568
            // stop processing, if the filename doesn't match
569
            if (!$this->match($filename)) {
570
                return;
571
            }
572
573
            // load the system logger instance
574
            $systemLogger = $this->getSystemLogger();
575
576
            // prepare the flag filenames
577
            $inProgressFilename = sprintf('%s.inProgress', $filename);
578
            $importedFilename = sprintf('%s.imported', $filename);
579
            $failedFilename = sprintf('%s.failed', $filename);
580
581
            // query whether or not the file has already been imported
582
            if (is_file($failedFilename) ||
583
                is_file($importedFilename) ||
584
                is_file($inProgressFilename)
585
            ) {
586
                // log a debug message and exit
587
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
588
                return;
589
            }
590
591
            // flag file as in progress
592
            touch($inProgressFilename);
593
594
            // track the start time
595
            $startTime = microtime(true);
596
597
            // initialize serial and filename
598
            $this->setSerial($serial);
599
            $this->setFilename($filename);
600
601
            // load the system logger
602
            $systemLogger = $this->getSystemLogger();
603
604
            // initialize the global global data to import a bunch
605
            $this->setUp();
606
607
            // initialize the lexer instance itself
608
            $lexer = new Lexer($this->getLexerConfig());
609
610
            // initialize the interpreter
611
            $interpreter = new Interpreter();
612
            $interpreter->addObserver(array($this, 'importRow'));
613
614
            // query whether or not we want to use the strict mode
615
            if (!$this->getConfiguration()->isStrictMode()) {
616
                $interpreter->unstrict();
617
            }
618
619
            // log a message that the file has to be imported
620
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
621
622
            // parse the CSV file to be imported
623
            $lexer->parse($filename, $interpreter);
624
625
            // track the time needed for the import in seconds
626
            $endTime = microtime(true) - $startTime;
627
628
            // clean up the data after importing the bunch
629
            $this->tearDown();
630
631
            // log a message that the file has successfully been imported
632
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
633
634
            // rename flag file, because import has been successfull
635
            rename($inProgressFilename, $importedFilename);
636
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
637
        } catch (\Exception $e) {
638
            // rename the flag file, because import failed and write the stack trace
639
            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...
640
            file_put_contents($failedFilename, $e->__toString());
641
642
            // clean up the data after importing the bunch
643
            $this->tearDown();
644
645
            // throw a new exception
646
            throw new \Exception(sprintf('%s in line number %d', $e->getMessage(), $this->lineNumber), null, $e);
647
        }
648
    }
649
650
    /**
651
     * This method queries whether or not the passed filename matches
652
     * the pattern, based on the subjects configured prefix.
653
     *
654
     * @param string $filename The filename to match
655
     *
656
     * @return boolean TRUE if the filename matches, else FALSE
657
     */
658
    protected function match($filename)
659
    {
660
661
        // prepare the pattern to query whether the file has to be processed or not
662
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
663
664
        // stop processing, if the filename doesn't match
665
        return (boolean) preg_match($pattern, $filename);
666
    }
667
668
    /**
669
     * Initialize and return the lexer configuration.
670
     *
671
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
672
     */
673
    protected function getLexerConfig()
674
    {
675
676
        // initialize the lexer configuration
677
        $config = new LexerConfig();
678
679
        // query whether or not a delimiter character has been configured
680
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
681
            $config->setDelimiter($delimiter);
682
        }
683
684
        // query whether or not a custom escape character has been configured
685
        if ($escape = $this->getConfiguration()->getEscape()) {
686
            $config->setEscape($escape);
687
        }
688
689
        // query whether or not a custom enclosure character has been configured
690
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
691
            $config->setEnclosure($enclosure);
692
        }
693
694
        // query whether or not a custom source charset has been configured
695
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
696
            $config->setFromCharset($fromCharset);
697
        }
698
699
        // query whether or not a custom target charset has been configured
700
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
701
            $config->setToCharset($toCharset);
702
        }
703
704
        // return the lexer configuratio
705
        return $config;
706
    }
707
708
    /**
709
     * Imports the passed row into the database.
710
     *
711
     * If the import failed, the exception will be catched and logged,
712
     * but the import process will be continued.
713
     *
714
     * @param array $row The row with the data to be imported
715
     *
716
     * @return void
717
     */
718
    public function importRow(array $row)
719
    {
720
721
        // raise the line number
722
        $this->lineNumber++;
723
724
        // initialize the headers with the columns from the first line
725
        if (sizeof($this->getHeaders()) === 0) {
726
            $this->setHeaders(array_flip($row));
727
            return;
728
        }
729
730
        // process the observers
731
        foreach ($this->getObservers() as $observers) {
732
            // invoke the pre-import/import and post-import observers
733
            foreach ($observers as $observer) {
734
                if ($observer instanceof ObserverInterface) {
735
                    $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...
736
                }
737
            }
738
        }
739
    }
740
}
741