Completed
Push — 16.x ( 408f6d...f4dd8c )
by Tim
01:23
created

Simple::getModules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * TechDivision\Import\App\Simple
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-app-simple
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\App;
22
23
use Psr\Log\LogLevel;
24
use League\Event\EmitterInterface;
25
use Doctrine\Common\Collections\Collection;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Helper\FormatterHelper;
28
use Symfony\Component\DependencyInjection\TaggedContainerInterface;
29
use TechDivision\Import\Utils\LoggerKeys;
30
use TechDivision\Import\Utils\EventNames;
31
use TechDivision\Import\ApplicationInterface;
32
use TechDivision\Import\App\Utils\DependencyInjectionKeys;
33
use TechDivision\Import\ConfigurationInterface;
34
use TechDivision\Import\Exceptions\LineNotFoundException;
35
use TechDivision\Import\Exceptions\FileNotFoundException;
36
use TechDivision\Import\Exceptions\ImportAlreadyRunningException;
37
use TechDivision\Import\Services\ImportProcessorInterface;
38
use TechDivision\Import\Services\RegistryProcessorInterface;
39
40
/**
41
 * The M2IF - Simple Application implementation.
42
 *
43
 * This is a example application implementation that should give developers an impression
44
 * on how the M2IF could be used to implement their own Magento 2 importer.
45
 *
46
 * @author    Tim Wagner <[email protected]>
47
 * @copyright 2016 TechDivision GmbH <[email protected]>
48
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
49
 * @link      https://github.com/techdivision/import-app-simple
50
 * @link      http://www.techdivision.com
51
 */
52
class Simple implements ApplicationInterface
53
{
54
55
    /**
56
     * The default style to write messages to the symfony console.
57
     *
58
     * @var string
59
     */
60
    const DEFAULT_STYLE = 'info';
61
62
    /**
63
     * The log level => console style mapping.
64
     *
65
     * @var array
66
     */
67
    protected $logLevelStyleMapping = array(
68
        LogLevel::INFO      => 'info',
69
        LogLevel::DEBUG     => 'comment',
70
        LogLevel::ERROR     => 'error',
71
        LogLevel::ALERT     => 'error',
72
        LogLevel::CRITICAL  => 'error',
73
        LogLevel::EMERGENCY => 'error',
74
        LogLevel::WARNING   => 'error',
75
        LogLevel::NOTICE    => 'info'
76
    );
77
78
    /**
79
     * The PID for the running processes.
80
     *
81
     * @var array
82
     */
83
    protected $pid;
84
85
    /**
86
     * The actions unique serial.
87
     *
88
     * @var string
89
     */
90
    protected $serial;
91
92
    /**
93
     * The array with the system logger instances.
94
     *
95
     * @var \Doctrine\Common\Collections\Collection
96
     */
97
    protected $systemLoggers;
98
99
    /**
100
     * The RegistryProcessor instance to handle running threads.
101
     *
102
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
103
     */
104
    protected $registryProcessor;
105
106
    /**
107
     * The processor to read/write the necessary import data.
108
     *
109
     * @var \TechDivision\Import\Services\ImportProcessorInterface
110
     */
111
    protected $importProcessor;
112
113
    /**
114
     * The DI container builder instance.
115
     *
116
     * @var \Symfony\Component\DependencyInjection\TaggedContainerInterface
117
     */
118
    protected $container;
119
120
    /**
121
     * The system configuration.
122
     *
123
     * @var \TechDivision\Import\ConfigurationInterface
124
     */
125
    protected $configuration;
126
127
    /**
128
     * The output stream to write console information to.
129
     *
130
     * @var \Symfony\Component\Console\Output\OutputInterface
131
     */
132
    protected $output;
133
134
    /**
135
     * The plugins to be processed.
136
     *
137
     * @var array
138
     */
139
    protected $plugins = array();
140
141
    /**
142
     * The flag that stop's processing the operation.
143
     *
144
     * @var boolean
145
     */
146
    protected $stopped = false;
147
148
    /**
149
     * The filehandle for the PID file.
150
     *
151
     * @var resource
152
     */
153
    protected $fh;
154
155
    /**
156
     * The array with the module instances.
157
     *
158
     * @var \TechDivision\Import\Modules\ModuleInterface[]
159
     */
160
    protected $modules;
161
162
    /**
163
     * The event emitter instance.
164
     *
165
     * @var \League\Event\EmitterInterface
166
     */
167
    protected $emitter;
168
169
    /**
170
     * The constructor to initialize the instance.
171
     *
172
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container         The DI container instance
173
     * @param \TechDivision\Import\Services\RegistryProcessorInterface        $registryProcessor The registry processor instance
174
     * @param \TechDivision\Import\Services\ImportProcessorInterface          $importProcessor   The import processor instance
175
     * @param \TechDivision\Import\ConfigurationInterface                     $configuration     The system configuration
176
     * @param \Symfony\Component\Console\Output\OutputInterface               $output            The output instance
177
     * @param \Doctrine\Common\Collections\Collection                         $systemLoggers     The array with the system logger instances
178
     * @param \League\Event\EmitterInterface                                  $emitter           The event emitter instance
179
     * @param \Traversable                                                    $modules           The modules that provides the business logic
180
     */
181
    public function __construct(
182
        TaggedContainerInterface $container,
183
        RegistryProcessorInterface $registryProcessor,
184
        ImportProcessorInterface $importProcessor,
185
        ConfigurationInterface $configuration,
186
        OutputInterface $output,
187
        Collection $systemLoggers,
188
        EmitterInterface $emitter,
189
        \Traversable $modules
190
    ) {
191
192
        // register the shutdown function
193
        register_shutdown_function(array($this, 'shutdown'));
194
195
        // initialize the instance with the passed values
196
        $this->setOutput($output);
197
        $this->setEmitter($emitter);
198
        $this->setModules($modules);
199
        $this->setContainer($container);
200
        $this->setConfiguration($configuration);
201
        $this->setSystemLoggers($systemLoggers);
202
        $this->setImportProcessor($importProcessor);
203
        $this->setRegistryProcessor($registryProcessor);
204
    }
205
206
    /**
207
     * Set's the event emitter instance.
208
     *
209
     * @param \League\Event\EmitterInterface $emitter The event emitter instance
210
     *
211
     * @return void
212
     */
213
    public function setEmitter(EmitterInterface $emitter)
214
    {
215
        $this->emitter = $emitter;
216
    }
217
218
    /**
219
     * Return's the event emitter instance.
220
     *
221
     * @return \League\Event\EmitterInterface The event emitter instance
222
     */
223
    public function getEmitter()
224
    {
225
        return $this->emitter;
226
    }
227
228
    /**
229
     * Set's the container instance.
230
     *
231
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container The container instance
232
     *
233
     * @return void
234
     */
235
    public function setContainer(TaggedContainerInterface $container)
236
    {
237
        $this->container = $container;
238
    }
239
240
    /**
241
     * Return's the container instance.
242
     *
243
     * @return \Symfony\Component\DependencyInjection\TaggedContainerInterface The container instance
244
     */
245
    public function getContainer()
246
    {
247
        return $this->container;
248
    }
249
250
    /**
251
     * Set's the output stream to write console information to.
252
     *
253
     * @param \Symfony\Component\Console\Output\OutputInterface $output The output stream
254
     *
255
     * @return void
256
     */
257
    public function setOutput(OutputInterface $output)
258
    {
259
        $this->output = $output;
260
    }
261
262
    /**
263
     * Return's the output stream to write console information to.
264
     *
265
     * @return \Symfony\Component\Console\Output\OutputInterface The output stream
266
     */
267
    public function getOutput()
268
    {
269
        return $this->output;
270
    }
271
272
    /**
273
     * Set's the system configuration.
274
     *
275
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
276
     *
277
     * @return void
278
     */
279
    public function setConfiguration(ConfigurationInterface $configuration)
280
    {
281
        $this->configuration = $configuration;
282
    }
283
284
    /**
285
     * Return's the system configuration.
286
     *
287
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
288
     */
289
    public function getConfiguration()
290
    {
291
        return $this->configuration;
292
    }
293
294
    /**
295
     * Set's the RegistryProcessor instance to handle the running threads.
296
     *
297
     * @param \TechDivision\Import\Services\RegistryProcessor $registryProcessor The registry processor instance
298
     *
299
     * @return void
300
     */
301
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
302
    {
303
        $this->registryProcessor = $registryProcessor;
304
    }
305
306
    /**
307
     * Return's the RegistryProcessor instance to handle the running threads.
308
     *
309
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
310
     */
311
    public function getRegistryProcessor()
312
    {
313
        return $this->registryProcessor;
314
    }
315
316
    /**
317
     * Set's the import processor instance.
318
     *
319
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
320
     *
321
     * @return void
322
     */
323
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
324
    {
325
        $this->importProcessor = $importProcessor;
326
    }
327
328
    /**
329
     * Return's the import processor instance.
330
     *
331
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
332
     */
333
    public function getImportProcessor()
334
    {
335
        return $this->importProcessor;
336
    }
337
338
    /**
339
     * The array with the system loggers.
340
     *
341
     * @param \Doctrine\Common\Collections\Collection $systemLoggers The system logger instances
342
     *
343
     * @return void
344
     */
345
    public function setSystemLoggers(Collection $systemLoggers)
346
    {
347
        $this->systemLoggers = $systemLoggers;
348
    }
349
350
    /**
351
     * Set's the module instances.
352
     *
353
     * @param \Traversable $modules The modules instances
354
     *
355
     * @return void
356
     */
357
    public function setModules(\Traversable $modules)
358
    {
359
        $this->modules = $modules;
0 ignored issues
show
Documentation Bug introduced by
It seems like $modules of type object<Traversable> is incompatible with the declared type array<integer,object<Tec...dules\ModuleInterface>> of property $modules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
360
    }
361
362
    /**
363
     * Return's the module instances.
364
     *
365
     * @return \Traversable The module instances
366
     */
367
    public function getModules()
368
    {
369
        return $this->modules;
370
    }
371
372
    /**
373
     * Return's the logger with the passed name, by default the system logger.
374
     *
375
     * @param string $name The name of the requested system logger
376
     *
377
     * @return \Psr\Log\LoggerInterface The logger instance
378
     * @throws \Exception Is thrown, if the requested logger is NOT available
379
     */
380
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
381
    {
382
383
        // query whether or not, the requested logger is available
384
        if (isset($this->systemLoggers[$name])) {
385
            return $this->systemLoggers[$name];
386
        }
387
388
        // throw an exception if the requested logger is NOT available
389
        throw new \Exception(
390
            sprintf(
391
                'The requested logger \'%s\' is not available',
392
                $name
393
            )
394
        );
395
    }
396
397
    /**
398
     * Returns the actual application version.
399
     *
400
     * @return string The application's version
401
     */
402
    public function getVersion()
403
    {
404
        return $this->getContainer()->get(DependencyInjectionKeys::APPLICATION)->getVersion();
405
    }
406
407
    /**
408
     * Returns the actual application name.
409
     *
410
     * @return string The application's name
411
     */
412
    public function getName()
413
    {
414
        return $this->getContainer()->get(DependencyInjectionKeys::APPLICATION)->getName();
415
    }
416
417
    /**
418
     * Query whether or not the system logger with the passed name is available.
419
     *
420
     * @param string $name The name of the requested system logger
421
     *
422
     * @return boolean TRUE if the logger with the passed name exists, else FALSE
423
     */
424
    public function hasSystemLogger($name = LoggerKeys::SYSTEM)
425
    {
426
        return isset($this->systemLoggers[$name]);
427
    }
428
429
    /**
430
     * Return's the array with the system logger instances.
431
     *
432
     * @return \Doctrine\Common\Collections\Collection The logger instance
433
     */
434
    public function getSystemLoggers()
435
    {
436
        return $this->systemLoggers;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->systemLoggers; (Doctrine\Common\Collections\Collection) is incompatible with the return type declared by the interface TechDivision\Import\Appl...rface::getSystemLoggers of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
437
    }
438
439
    /**
440
     * Return's the unique serial for this import process.
441
     *
442
     * @return string The unique serial
443
     */
444
    public function getSerial()
445
    {
446
        return $this->serial;
447
    }
448
449
    /**
450
     * The shutdown handler to catch fatal errors.
451
     *
452
     * This method is need to make sure, that an existing PID file will be removed
453
     * if a fatal error has been triggered.
454
     *
455
     * @return void
456
     */
457
    public function shutdown()
458
    {
459
460
        // check if there was a fatal error caused shutdown
461
        if ($lastError = error_get_last()) {
462
            // initialize error type and message
463
            $type = 0;
464
            $message = '';
465
            // extract the last error values
466
            extract($lastError);
467
            // query whether we've a fatal/user error
468
            if ($type === E_ERROR || $type === E_USER_ERROR) {
469
                // clean-up the PID file
470
                $this->unlock();
471
                // log the fatal error message
472
                $this->log($message, LogLevel::ERROR);
473
            }
474
        }
475
    }
476
477
    /**
478
     * Persist the UUID of the actual import process to the PID file.
479
     *
480
     * @return void
481
     * @throws \Exception Is thrown, if the PID can not be locked or the PID can not be added
482
     * @throws \TechDivision\Import\Exceptions\ImportAlreadyRunningException Is thrown, if a import process is already running
483
     */
484
    public function lock()
485
    {
486
487
        // query whether or not, the PID has already been set
488
        if ($this->pid === $this->getSerial()) {
489
            return;
490
        }
491
492
        // if not, initialize the PID
493
        $this->pid = $this->getSerial();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getSerial() of type string is incompatible with the declared type array of property $pid.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
494
495
        // open the PID file
496
        $this->fh = fopen($filename = $this->getPidFilename(), 'a+');
497
498
        // try to lock the PID file exclusive
499
        if (!flock($this->fh, LOCK_EX|LOCK_NB)) {
500
            throw new ImportAlreadyRunningException(sprintf('PID file %s is already in use', $filename));
501
        }
502
503
        // append the PID to the PID file
504
        if (fwrite($this->fh, $this->pid . PHP_EOL) === false) {
505
            throw new \Exception(sprintf('Can\'t write PID %s to PID file %s', $this->pid, $filename));
506
        }
507
    }
508
509
    /**
510
     * Remove's the UUID of the actual import process from the PID file.
511
     *
512
     * @return void
513
     * @throws \Exception Is thrown, if the PID can not be removed
514
     */
515
    public function unlock()
516
    {
517
        try {
518
            // remove the PID from the PID file if set
519
            if ($this->pid === $this->getSerial() && is_resource($this->fh)) {
520
                // remove the PID from the file
521
                $this->removeLineFromFile($this->pid, $this->fh);
522
523
                // finally unlock/close the PID file
524
                flock($this->fh, LOCK_UN);
525
                fclose($this->fh);
526
527
                // if the PID file is empty, delete the file
528
                if (filesize($filename = $this->getPidFilename()) === 0) {
529
                    unlink($filename);
530
                }
531
            }
532
        } catch (FileNotFoundException $fnfe) {
533
            $this->getSystemLogger()->notice(sprintf('PID file %s doesn\'t exist', $this->getPidFilename()));
534
        } catch (LineNotFoundException $lnfe) {
535
            $this->getSystemLogger()->notice(sprintf('PID %s is can not be found in PID file %s', $this->pid, $this->getPidFilename()));
536
        } catch (\Exception $e) {
537
            throw new \Exception(sprintf('Can\'t remove PID %s from PID file %s', $this->pid, $this->getPidFilename()), null, $e);
538
        }
539
    }
540
541
    /**
542
     * Remove's the passed line from the file with the passed name.
543
     *
544
     * @param string   $line The line to be removed
545
     * @param resource $fh   The file handle of the file the line has to be removed
546
     *
547
     * @return void
548
     * @throws \Exception Is thrown, if the file doesn't exists, the line is not found or can not be removed
549
     */
550
    public function removeLineFromFile($line, $fh)
551
    {
552
553
        // initialize the array for the PIDs found in the PID file
554
        $lines = array();
555
556
        // initialize the flag if the line has been found
557
        $found = false;
558
559
        // rewind the file pointer
560
        rewind($fh);
561
562
        // read the lines with the PIDs from the PID file
563
        while (($buffer = fgets($fh, 4096)) !== false) {
564
            // remove the new line
565
            $buffer = trim($buffer);
566
            // if the line is the one to be removed, ignore the line
567
            if ($line === $buffer) {
568
                $found = true;
569
                continue;
570
            }
571
572
            // add the found PID to the array
573
            $lines[] = $buffer;
574
        }
575
576
        // query whether or not, we found the line
577
        if (!$found) {
578
            throw new LineNotFoundException(sprintf('Line %s can not be found', $line));
579
        }
580
581
        // empty the file and rewind the file pointer
582
        ftruncate($fh, 0);
583
        rewind($fh);
584
585
        // append the existing lines to the file
586
        foreach ($lines as $ln) {
587
            if (fwrite($fh, $ln . PHP_EOL) === false) {
588
                throw new \Exception(sprintf('Can\'t write %s to file', $ln));
589
            }
590
        }
591
    }
592
593
    /**
594
     * Process the given operation.
595
     *
596
     * @param string $serial The unique serial of the actual import process
597
     *
598
     * @return void
599
     * @throws \Exception Is thrown if the operation can't be finished successfully
600
     */
601
    public function process($serial)
602
    {
603
604
        try {
605
            // track the start time
606
            $startTime = microtime(true);
607
608
            // set the serial for this import process
609
            $this->serial = $serial;
610
611
            // invoke the event that has to be fired before the application start's the transaction
612
            // (if single transaction mode has been activated)
613
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
614
615
            // start the transaction, if single transaction mode has been configured
616
            if ($this->getConfiguration()->isSingleTransaction()) {
617
                $this->getImportProcessor()->getConnection()->beginTransaction();
618
            }
619
620
            // prepare the global data for the import process
621
            $this->setUp();
622
623
            // process the modules
624
            foreach ($this->getModules() as $module) {
625
                $module->process();
626
            }
627
628
            // tear down the  instance
629
            $this->tearDown();
630
631
            // commit the transaction, if single transation mode has been configured
632
            if ($this->getConfiguration()->isSingleTransaction()) {
633
                $this->getImportProcessor()->getConnection()->commit();
634
            }
635
636
            // track the time needed for the import in seconds
637
            $endTime = microtime(true) - $startTime;
638
639
            // log a message that import has been finished
640
            $this->log(
641
                sprintf(
642
                    'Successfully finished import with serial %s in %f s',
643
                    $this->getSerial(),
644
                    $endTime
645
                ),
646
                LogLevel::INFO
647
            );
648
649
            // invoke the event that has to be fired before the application has the transaction
650
            // committed successfully (if single transaction mode has been activated)
651
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
652
        } catch (ImportAlreadyRunningException $iare) {
653
            // tear down
654
            $this->tearDown();
655
656
            // rollback the transaction, if single transaction mode has been configured
657
            if ($this->getConfiguration()->isSingleTransaction()) {
658
                $this->getImportProcessor()->getConnection()->rollBack();
659
            }
660
661
            // invoke the event that has to be fired after the application rollbacked the
662
            // transaction (if single transaction mode has been activated)
663
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_FAILURE, $this, $iare);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
664
665
            // finally, if a PID has been set (because CSV files has been found),
666
            // remove it from the PID file to unlock the importer
667
            $this->unlock();
668
669
            // track the time needed for the import in seconds
670
            $endTime = microtime(true) - $startTime;
671
672
            // log a warning, because another import process is already running
673
            $this->getSystemLogger()->warning($iare->__toString());
674
675
            // log a message that import has been finished
676
            $this->getSystemLogger()->info(
677
                sprintf(
678
                    'Can\'t finish import with serial because another import process is running %s in %f s',
679
                    $this->getSerial(),
680
                    $endTime
681
                )
682
            );
683
684
            // re-throw the exception
685
            throw $iare;
686
        } catch (\Exception $e) {
687
            // tear down
688
            $this->tearDown();
689
690
            // rollback the transaction, if single transaction mode has been configured
691
            if ($this->getConfiguration()->isSingleTransaction()) {
692
                $this->getImportProcessor()->getConnection()->rollBack();
693
            }
694
695
            // invoke the event that has to be fired after the application rollbacked the
696
            // transaction (if single transaction mode has been activated)
697
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_FAILURE, $this, $e);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
698
699
            // finally, if a PID has been set (because CSV files has been found),
700
            // remove it from the PID file to unlock the importer
701
            $this->unlock();
702
703
            // track the time needed for the import in seconds
704
            $endTime = microtime(true) - $startTime;
705
706
            // log a message that the file import failed
707
            foreach ($this->systemLoggers as $systemLogger) {
708
                $systemLogger->error($e->__toString());
709
            }
710
711
            // log a message that import has been finished
712
            $this->getSystemLogger()->info(
713
                sprintf(
714
                    'Can\'t finish import with serial %s in %f s',
715
                    $this->getSerial(),
716
                    $endTime
717
                )
718
            );
719
720
            // re-throw the exception
721
            throw $e;
722
        }
723
    }
724
725
    /**
726
     * Stop processing the operation.
727
     *
728
     * @param string $reason The reason why the operation has been stopped
729
     *
730
     * @return void
731
     */
732
    public function stop($reason)
733
    {
734
735
        // log a message that the operation has been stopped
736
        $this->log($reason, LogLevel::INFO);
737
738
        // stop processing the plugins by setting the flag to TRUE
739
        $this->stopped = true;
740
    }
741
742
    /**
743
     * Return's TRUE if the operation has been stopped, else FALSE.
744
     *
745
     * @return boolean TRUE if the process has been stopped, else FALSE
746
     */
747
    public function isStopped()
748
    {
749
        return $this->stopped;
750
    }
751
752
    /**
753
     * Gets a service.
754
     *
755
     * @param string $id The service identifier
756
     *
757
     * @return object The associated service
758
     *
759
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException When a circular reference is detected
760
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException          When the service is not defined
761
     */
762
    public function get($id)
763
    {
764
        return $this->getContainer()->get($id);
765
    }
766
767
    /**
768
     * Returns true if the container can return an entry for the given identifier.
769
     * Returns false otherwise.
770
     *
771
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
772
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
773
     *
774
     * @param string $id Identifier of the entry to look for.
775
     *
776
     * @return bool
777
     */
778
    public function has($id)
779
    {
780
        return $this->getContainer()->has($id);
781
    }
782
783
    /**
784
     * Lifecycle callback that will be inovked before the
785
     * import process has been started.
786
     *
787
     * @return void
788
     */
789
    protected function setUp()
790
    {
791
        $this->getEmitter()->emit(EventNames::APP_SET_UP, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
792
    }
793
794
    /**
795
     * Lifecycle callback that will be inovked after the
796
     * import process has been finished.
797
     *
798
     * @return void
799
     */
800
    protected function tearDown()
801
    {
802
        $this->getEmitter()->emit(EventNames::APP_TEAR_DOWN, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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...
803
    }
804
805
    /**
806
     * Simple method that writes the passed method the the console and the
807
     * system logger, if configured and a log level has been passed.
808
     *
809
     * @param string $msg      The message to log
810
     * @param string $logLevel The log level to use
811
     *
812
     * @return void
813
     */
814
    public function log($msg, $logLevel = null)
815
    {
816
817
        // initialize the formatter helper
818
        $helper = new FormatterHelper();
819
820
        // map the log level to the console style
821
        $style = $this->mapLogLevelToStyle($logLevel);
822
823
        // format the message, according to the passed log level and write it to the console
824
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);
825
826
        // log the message if a log level has been passed
827
        if ($logLevel && $systemLogger = $this->getSystemLogger()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $logLevel of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
828
            $systemLogger->log($logLevel, $msg);
829
        }
830
    }
831
832
    /**
833
     * Map's the passed log level to a valid symfony console style.
834
     *
835
     * @param string $logLevel The log level to map
836
     *
837
     * @return string The apropriate symfony console style
838
     */
839
    protected function mapLogLevelToStyle($logLevel)
840
    {
841
842
        // query whether or not the log level is mapped
843
        if (isset($this->logLevelStyleMapping[$logLevel])) {
844
            return $this->logLevelStyleMapping[$logLevel];
845
        }
846
847
        // return the default style => info
848
        return Simple::DEFAULT_STYLE;
849
    }
850
851
    /**
852
     * Return's the PID filename to use.
853
     *
854
     * @return string The PID filename
855
     */
856
    protected function getPidFilename()
857
    {
858
        return $this->getConfiguration()->getPidFilename();
859
    }
860
}
861