Completed
Pull Request — master (#14)
by Tim
10:00
created

AbstractSubject::registerObserver()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 6
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
37
/**
38
 * An abstract subject implementation.
39
 *
40
 * @author    Tim Wagner <[email protected]>
41
 * @copyright 2016 TechDivision GmbH <[email protected]>
42
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
43
 * @link      https://github.com/techdivision/import
44
 * @link      http://www.techdivision.com
45
 */
46
abstract class AbstractSubject implements SubjectInterface
47
{
48
49
    /**
50
     * The root directory for the virtual filesystem.
51
     *
52
     * @var string
53
     */
54
    protected $rootDir;
55
56
    /**
57
     * The system configuration.
58
     *
59
     * @var \TechDivision\Import\Configuration\SubjectInterface
60
     */
61
    protected $configuration;
62
63
    /**
64
     * The system logger implementation.
65
     *
66
     * @var \Psr\Log\LoggerInterface
67
     */
68
    protected $systemLogger;
69
70
    /**
71
     * The RegistryProcessor instance to handle running threads.
72
     *
73
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
74
     */
75
    protected $registryProcessor;
76
77
    /**
78
     * The actions unique serial.
79
     *
80
     * @var string
81
     */
82
    protected $serial;
83
84
    /**
85
     * The name of the file to be imported.
86
     *
87
     * @var string
88
     */
89
    protected $filename;
90
91
    /**
92
     * Array with the subject's observers.
93
     *
94
     * @var array
95
     */
96
    protected $observers = array();
97
98
    /**
99
     * Array with the subject's callbacks.
100
     *
101
     * @var array
102
     */
103
    protected $callbacks = array();
104
105
    /**
106
     * Contain's the column names from the header line.
107
     *
108
     * @var array
109
     */
110
    protected $headers = array();
111
112
    /**
113
     * The virtual filesystem instance.
114
     *
115
     * @var \League\Flysystem\FilesystemInterface
116
     */
117
    protected $filesystem;
118
119
    /**
120
     * Set's the array containing header row.
121
     *
122
     * @param array $headers The array with the header row
123
     *
124
     * @return void
125
     */
126
    public function setHeaders(array $headers)
127
    {
128
        $this->headers = $headers;
129
    }
130
131
    /**
132
     * Return's the array containing header row.
133
     *
134
     * @return array The array with the header row
135
     */
136
    public function getHeaders()
137
    {
138
        return $this->headers;
139
    }
140
141
    /**
142
     * Queries whether or not the header with the passed name is available.
143
     *
144
     * @param string $name The header name to query
145
     *
146
     * @return boolean TRUE if the header is available, else FALSE
147
     */
148
    public function hasHeader($name)
149
    {
150
        return isset($this->headers[$name]);
151
    }
152
153
    /**
154
     * Return's the header value for the passed name.
155
     *
156
     * @param string $name The name of the header to return the value for
157
     *
158
     * @return mixed The header value
159
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
160
     */
161
    public function getHeader($name)
162
    {
163
164
        // query whether or not, the header is available
165
        if (isset($this->headers[$name])) {
166
            return $this->headers[$name];
167
        }
168
169
        // throw an exception, if not
170
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
171
    }
172
173
    /**
174
     * Add's the header with the passed name and position, if not NULL.
175
     *
176
     * @param string $name The header name to add
177
     *
178
     * @return integer The new headers position
179
     */
180
    public function addHeader($name)
181
    {
182
183
        // add the header
184
        $this->headers[$name] = $position = sizeof($this->headers);
185
186
        // return the new header's position
187
        return $position;
188
    }
189
190
    /**
191
     * Set's the system configuration.
192
     *
193
     * @param \TechDivision\Import\Configuration\Subject $configuration The system configuration
194
     *
195
     * @return void
196
     */
197
    public function setConfiguration(SubjectConfigurationInterface $configuration)
198
    {
199
        $this->configuration = $configuration;
200
    }
201
202
    /**
203
     * Return's the system configuration.
204
     *
205
     * @return \TechDivision\Import\Configuration\SubjectInterface The system configuration
206
     */
207
    public function getConfiguration()
208
    {
209
        return $this->configuration;
210
    }
211
212
    /**
213
     * Set's the system logger.
214
     *
215
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
216
     *
217
     * @return void
218
     */
219
    public function setSystemLogger(LoggerInterface $systemLogger)
220
    {
221
        $this->systemLogger = $systemLogger;
222
    }
223
224
    /**
225
     * Return's the system logger.
226
     *
227
     * @return \Psr\Log\LoggerInterface The system logger instance
228
     */
229
    public function getSystemLogger()
230
    {
231
        return $this->systemLogger;
232
    }
233
234
    /**
235
     * Set's root directory for the virtual filesystem.
236
     *
237
     * @param string $rootDir The root directory for the virtual filesystem
238
     *
239
     * @return void
240
     */
241
    public function setRootDir($rootDir)
242
    {
243
        $this->rootDir = $rootDir;
244
    }
245
246
    /**
247
     * Return's the root directory for the virtual filesystem.
248
     *
249
     * @return string The root directory for the virtual filesystem
250
     */
251
    public function getRootDir()
252
    {
253
        return $this->rootDir;
254
    }
255
256
    /**
257
     * Set's the virtual filesystem instance.
258
     *
259
     * @param \League\Flysystem\FilesystemInterface $filesystem The filesystem instance
260
     *
261
     * @return void
262
     */
263
    public function setFilesystem(FilesystemInterface $filesystem)
264
    {
265
        $this->filesystem = $filesystem;
266
    }
267
268
    /**
269
     * Return's the virtual filesystem instance.
270
     *
271
     * @return \League\Flysystem\FilesystemInterface The filesystem instance
272
     */
273
    public function getFilesystem()
274
    {
275
        return $this->filesystem;
276
    }
277
278
    /**
279
     * Sets's the RegistryProcessor instance to handle the running threads.
280
     *
281
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
282
     *
283
     * @return void
284
     */
285
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
286
    {
287
        $this->registryProcessor = $registryProcessor;
288
    }
289
290
    /**
291
     * Return's the RegistryProcessor instance to handle the running threads.
292
     *
293
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
294
     */
295
    public function getRegistryProcessor()
296
    {
297
        return $this->registryProcessor;
298
    }
299
300
    /**
301
     * Set's the unique serial for this import process.
302
     *
303
     * @param string $serial The unique serial
304
     *
305
     * @return void
306
     */
307
    public function setSerial($serial)
308
    {
309
        $this->serial = $serial;
310
    }
311
312
    /**
313
     * Return's the unique serial for this import process.
314
     *
315
     * @return string The unique serial
316
     */
317
    public function getSerial()
318
    {
319
        return $this->serial;
320
    }
321
322
    /**
323
     * Set's the name of the file to import
324
     *
325
     * @param string $filename The filename
326
     *
327
     * @return void
328
     */
329
    public function setFilename($filename)
330
    {
331
        $this->filename = $filename;
332
    }
333
334
    /**
335
     * Return's the name of the file to import.
336
     *
337
     * @return string The filename
338
     */
339
    public function getFilename()
340
    {
341
        return $this->filename;
342
    }
343
344
    /**
345
     * Return's the source date format to use.
346
     *
347
     * @return string The source date format
348
     */
349
    public function getSourceDateFormat()
350
    {
351
        return $this->getConfiguration()->getSourceDateFormat();
352
    }
353
354
    /**
355
     * Return's the initialized PDO connection.
356
     *
357
     * @return \PDO The initialized PDO connection
358
     */
359
    public function getConnection()
360
    {
361
        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...
362
    }
363
364
    /**
365
     * Intializes the previously loaded global data for exactly one bunch.
366
     *
367
     * @return void
368
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
369
     */
370
    public function setUp()
371
    {
372
373
        // initialize the filesystems root directory
374
        $this->setRootDir(
375
            $this->getConfiguration()->getParam(ConfigurationKeys::ROOT_DIRECTORY, getcwd())
376
        );
377
378
        // initialize the filesystem
379
        $this->setFilesystem(new Filesystem(new Local($this->getRootDir())));
380
    }
381
382
    /**
383
     * This method tries to resolve the passed path and returns it. If the path
384
     * is relative, the actual working directory will be prepended.
385
     *
386
     * @param string $path The path to be resolved
387
     *
388
     * @return string The resolved path
389
     * @throws \InvalidArgumentException Is thrown, if the path can not be resolved
390
     */
391
    public function resolvePath($path)
392
    {
393
        // if we've an absolute path, return it immediately
394
        if ($this->getFilesystem()->has($path)) {
395
            return $path;
396
        }
397
398
        // try to prepend the actual working directory, assuming we've a relative path
399
        if ($this->getFilesystem()->has($path = getcwd() . DIRECTORY_SEPARATOR . $path)) {
400
            return $path;
401
        }
402
403
        // throw an exception if the passed directory doesn't exists
404
        throw new \InvalidArgumentException(
405
            sprintf('Directory %s doesn\'t exist', $path)
406
        );
407
    }
408
409
    /**
410
     * Clean up the global data after importing the variants.
411
     *
412
     * @return void
413
     */
414
    public function tearDown()
415
    {
416
    }
417
418
    /**
419
     * Register the passed observer with the specific type.
420
     *
421
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
422
     * @param string                                           $type     The type to register the observer with
423
     *
424
     * @return void
425
     */
426
    public function registerObserver(ObserverInterface $observer, $type)
427
    {
428
429
        // query whether or not the array with the callbacks for the
430
        // passed type has already been initialized, or not
431
        if (!isset($this->observers[$type])) {
432
            $this->observers[$type] = array();
433
        }
434
435
        // append the callback with the instance of the passed type
436
        $this->observers[$type][] = $observer;
437
    }
438
439
    /**
440
     * Register the passed callback with the specific type.
441
     *
442
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
443
     * @param string                                           $type     The type to register the callback with
444
     *
445
     * @return void
446
     */
447
    public function registerCallback(CallbackInterface $callback, $type)
448
    {
449
450
        // query whether or not the array with the callbacks for the
451
        // passed type has already been initialized, or not
452
        if (!isset($this->callbacks[$type])) {
453
            $this->callbacks[$type] = array();
454
        }
455
456
        // append the callback with the instance of the passed type
457
        $this->callbacks[$type][] = $callback;
458
    }
459
460
    /**
461
     * Return's the array with callbacks for the passed type.
462
     *
463
     * @param string $type The type of the callbacks to return
464
     *
465
     * @return array The callbacks
466
     */
467
    public function getCallbacksByType($type)
468
    {
469
470
        // initialize the array for the callbacks
471
        $callbacks = array();
472
473
        // query whether or not callbacks for the type are available
474
        if (isset($this->callbacks[$type])) {
475
            $callbacks = $this->callbacks[$type];
476
        }
477
478
        // return the array with the type's callbacks
479
        return $callbacks;
480
    }
481
482
    /**
483
     * Return's the array with the available observers.
484
     *
485
     * @return array The observers
486
     */
487
    public function getObservers()
488
    {
489
        return $this->observers;
490
    }
491
492
    /**
493
     * Return's the array with the available callbacks.
494
     *
495
     * @return array The callbacks
496
     */
497
    public function getCallbacks()
498
    {
499
        return $this->callbacks;
500
    }
501
502
    /**
503
     * Imports the content of the file with the passed filename.
504
     *
505
     * @param string $serial   The unique process serial
506
     * @param string $filename The filename to process
507
     *
508
     * @return void
509
     */
510
    public function import($serial, $filename)
511
    {
512
513
        try {
514
            // track the start time
515
            $startTime = microtime(true);
516
517
            // initialize serial and filename
518
            $this->setSerial($serial);
519
            $this->setFilename($filename);
520
521
            // load the system logger
522
            $systemLogger = $this->getSystemLogger();
523
524
            // initialize the global global data to import a bunch
525
            $this->setUp();
526
527
            // initialize the lexer instance itself
528
            $lexer = new Lexer($this->getLexerConfig());
529
530
            // initialize the interpreter
531
            $interpreter = new Interpreter();
532
            $interpreter->addObserver(array($this, 'importRow'));
533
534
            // query whether or not we want to use the strict mode
535
            if (!$this->getConfiguration()->isStrictMode()) {
536
                $interpreter->unstrict();
537
            }
538
539
            // log a message that the file has to be imported
540
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
541
542
            // parse the CSV file to be imported
543
            $lexer->parse($filename, $interpreter);
544
545
            // track the time needed for the import in seconds
546
            $endTime = microtime(true) - $startTime;
547
548
            // log a message that the file has successfully been imported
549
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
550
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
551
        } catch (\Exception $e) {
552
            // log a message with the stack trace
553
            $systemLogger->error($e->__toString());
0 ignored issues
show
Bug introduced by
The variable $systemLogger 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...
554
555
            // re-throw the exception
556
            throw $e;
557
        }
558
559
        // clean up the data after importing the bunch
560
        $this->tearDown();
561
    }
562
563
    /**
564
     * Initialize and return the lexer configuration.
565
     *
566
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
567
     */
568
    protected function getLexerConfig()
569
    {
570
571
        // initialize the lexer configuration
572
        $config = new LexerConfig();
573
574
        // query whether or not a delimiter character has been configured
575
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
576
            $config->setDelimiter($delimiter);
577
        }
578
579
        // query whether or not a custom escape character has been configured
580
        if ($escape = $this->getConfiguration()->getEscape()) {
581
            $config->setEscape($escape);
582
        }
583
584
        // query whether or not a custom enclosure character has been configured
585
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
586
            $config->setEnclosure($enclosure);
587
        }
588
589
        // query whether or not a custom source charset has been configured
590
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
591
            $config->setFromCharset($fromCharset);
592
        }
593
594
        // query whether or not a custom target charset has been configured
595
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
596
            $config->setToCharset($toCharset);
597
        }
598
599
        // return the lexer configuratio
600
        return $config;
601
    }
602
603
    /**
604
     * Imports the passed row into the database.
605
     *
606
     * If the import failed, the exception will be catched and logged,
607
     * but the import process will be continued.
608
     *
609
     * @param array $row The row with the data to be imported
610
     *
611
     * @return void
612
     */
613
    public function importRow(array $row)
614
    {
615
616
        // initialize the headers with the columns from the first line
617
        if (sizeof($this->getHeaders()) === 0) {
618
            $this->setHeaders(array_flip($row));
619
            return;
620
        }
621
622
        // process the observers
623
        foreach ($this->getObservers() as $observers) {
624
            // invoke the pre-import/import and post-import observers
625
            foreach ($observers as $observer) {
626
                if ($observer instanceof ObserverInterface) {
627
                    $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...
628
                }
629
            }
630
        }
631
    }
632
}
633