Completed
Pull Request — master (#16)
by
unknown
03:10
created

Simple::shouldBeLogged()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.2
cc 4
eloc 5
nc 6
nop 2
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\LoggerInterface;
24
use Psr\Log\LogLevel;
25
use Rhumsaa\Uuid\Uuid;
26
use League\Event\EmitterInterface;
27
use Doctrine\Common\Collections\Collection;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Console\Helper\FormatterHelper;
30
use Symfony\Component\DependencyInjection\TaggedContainerInterface;
31
use TechDivision\Import\Utils\LoggerKeys;
32
use TechDivision\Import\Utils\EventNames;
33
use TechDivision\Import\Utils\RegistryKeys;
34
use TechDivision\Import\App\Utils\DependencyInjectionKeys;
35
use TechDivision\Import\ApplicationInterface;
36
use TechDivision\Import\ConfigurationInterface;
37
use TechDivision\Import\Plugins\PluginFactoryInterface;
38
use TechDivision\Import\Exceptions\LineNotFoundException;
39
use TechDivision\Import\Exceptions\FileNotFoundException;
40
use TechDivision\Import\Exceptions\ImportAlreadyRunningException;
41
use TechDivision\Import\Services\ImportProcessorInterface;
42
use TechDivision\Import\Services\RegistryProcessorInterface;
43
44
/**
45
 * The M2IF - Simple Application implementation.
46
 *
47
 * This is a example application implementation that should give developers an impression
48
 * on how the M2IF could be used to implement their own Magento 2 importer.
49
 *
50
 * @author    Tim Wagner <[email protected]>
51
 * @copyright 2016 TechDivision GmbH <[email protected]>
52
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
53
 * @link      https://github.com/techdivision/import-app-simple
54
 * @link      http://www.techdivision.com
55
 */
56
class Simple implements ApplicationInterface
57
{
58
59
    /**
60
     * The default style to write messages to the symfony console.
61
     *
62
     * @var string
63
     */
64
    const DEFAULT_STYLE = 'info';
65
66
    /**
67
     * The TechDivision company name as ANSI art.
68
     *
69
     * @var string
70
     */
71
    protected $ansiArt = ' _______        _     _____  _       _     _
72
|__   __|      | |   |  __ \(_)     (_)   (_)
73
   | | ___  ___| |__ | |  | |___   ___ ___ _  ___  _ __
74
   | |/ _ \/ __| \'_ \| |  | | \ \ / / / __| |/ _ \| \'_ \
75
   | |  __/ (__| | | | |__| | |\ V /| \__ \ | (_) | | | |
76
   |_|\___|\___|_| |_|_____/|_| \_/ |_|___/_|\___/|_| |_|
77
';
78
79
    /**
80
     * The log level => console style mapping.
81
     *
82
     * @var array
83
     */
84
    protected $logLevelStyleMapping = array(
85
        LogLevel::INFO      => 'info',
86
        LogLevel::DEBUG     => 'comment',
87
        LogLevel::ERROR     => 'error',
88
        LogLevel::ALERT     => 'error',
89
        LogLevel::CRITICAL  => 'error',
90
        LogLevel::EMERGENCY => 'error',
91
        LogLevel::WARNING   => 'error',
92
        LogLevel::NOTICE    => 'info'
93
    );
94
95
    /**
96
     * The PID for the running processes.
97
     *
98
     * @var array
99
     */
100
    protected $pid;
101
102
    /**
103
     * The actions unique serial.
104
     *
105
     * @var string
106
     */
107
    protected $serial;
108
109
    /**
110
     * The array with the system logger instances.
111
     *
112
     * @var \Doctrine\Common\Collections\Collection
113
     */
114
    protected $systemLoggers;
115
116
    /**
117
     * The RegistryProcessor instance to handle running threads.
118
     *
119
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
120
     */
121
    protected $registryProcessor;
122
123
    /**
124
     * The processor to read/write the necessary import data.
125
     *
126
     * @var \TechDivision\Import\Services\ImportProcessorInterface
127
     */
128
    protected $importProcessor;
129
130
    /**
131
     * The DI container builder instance.
132
     *
133
     * @var \Symfony\Component\DependencyInjection\TaggedContainerInterface
134
     */
135
    protected $container;
136
137
    /**
138
     * The system configuration.
139
     *
140
     * @var \TechDivision\Import\ConfigurationInterface
141
     */
142
    protected $configuration;
143
144
    /**
145
     * The output stream to write console information to.
146
     *
147
     * @var \Symfony\Component\Console\Output\OutputInterface
148
     */
149
    protected $output;
150
151
    /**
152
     * The plugins to be processed.
153
     *
154
     * @var array
155
     */
156
    protected $plugins = array();
157
158
    /**
159
     * The flag that stop's processing the operation.
160
     *
161
     * @var boolean
162
     */
163
    protected $stopped = false;
164
165
    /**
166
     * The filehandle for the PID file.
167
     *
168
     * @var resource
169
     */
170
    protected $fh;
171
172
    /**
173
     * The plugin factory instance.
174
     *
175
     * @var \TechDivision\Import\Plugins\PluginFactoryInterface
176
     */
177
    protected $pluginFactory;
178
179
    /**
180
     * The event emitter instance.
181
     *
182
     * @var \League\Event\EmitterInterface
183
     */
184
    protected $emitter;
185
186
    /**
187
     * The constructor to initialize the instance.
188
     *
189
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container         The DI container instance
190
     * @param \TechDivision\Import\Services\RegistryProcessorInterface        $registryProcessor The registry processor instance
191
     * @param \TechDivision\Import\Services\ImportProcessorInterface          $importProcessor   The import processor instance
192
     * @param \TechDivision\Import\ConfigurationInterface                     $configuration     The system configuration
193
     * @param \TechDivision\Import\Plugins\PluginFactoryInterface             $pluginFactory     The plugin factory instance
194
     * @param \Symfony\Component\Console\Output\OutputInterface               $output            The output instance
195
     * @param \Doctrine\Common\Collections\Collection                         $systemLoggers     The array with the system logger instances
196
     * @param \League\Event\EmitterInterface                                  $emitter           The event emitter instance
197
     */
198
    public function __construct(
199
        TaggedContainerInterface $container,
200
        RegistryProcessorInterface $registryProcessor,
201
        ImportProcessorInterface $importProcessor,
202
        ConfigurationInterface $configuration,
203
        PluginFactoryInterface $pluginFactory,
204
        OutputInterface $output,
205
        Collection $systemLoggers,
206
        EmitterInterface $emitter
207
    ) {
208
209
        // register the shutdown function
210
        register_shutdown_function(array($this, 'shutdown'));
211
212
        // initialize the instance with the passed values
213
        $this->setOutput($output);
214
        $this->setEmitter($emitter);
215
        $this->setContainer($container);
216
        $this->setConfiguration($configuration);
217
        $this->setSystemLoggers($systemLoggers);
218
        $this->setPluginFactory($pluginFactory);
219
        $this->setImportProcessor($importProcessor);
220
        $this->setRegistryProcessor($registryProcessor);
221
    }
222
223
    /**
224
     * Set's the event emitter instance.
225
     *
226
     * @param \League\Event\EmitterInterface $emitter The event emitter instance
227
     *
228
     * @return void
229
     */
230
    public function setEmitter(EmitterInterface $emitter)
231
    {
232
        $this->emitter = $emitter;
233
    }
234
235
    /**
236
     * Return's the event emitter instance.
237
     *
238
     * @return \League\Event\EmitterInterface The event emitter instance
239
     */
240
    public function getEmitter()
241
    {
242
        return $this->emitter;
243
    }
244
245
    /**
246
     * Set's the container instance.
247
     *
248
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container The container instance
249
     *
250
     * @return void
251
     */
252
    public function setContainer(TaggedContainerInterface $container)
253
    {
254
        $this->container = $container;
255
    }
256
257
    /**
258
     * Return's the container instance.
259
     *
260
     * @return \Symfony\Component\DependencyInjection\TaggedContainerInterface The container instance
261
     */
262
    public function getContainer()
263
    {
264
        return $this->container;
265
    }
266
267
    /**
268
     * Set's the output stream to write console information to.
269
     *
270
     * @param \Symfony\Component\Console\Output\OutputInterface $output The output stream
271
     *
272
     * @return void
273
     */
274
    public function setOutput(OutputInterface $output)
275
    {
276
        $this->output = $output;
277
    }
278
279
    /**
280
     * Return's the output stream to write console information to.
281
     *
282
     * @return \Symfony\Component\Console\Output\OutputInterface The output stream
283
     */
284
    public function getOutput()
285
    {
286
        return $this->output;
287
    }
288
289
    /**
290
     * Set's the system configuration.
291
     *
292
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
293
     *
294
     * @return void
295
     */
296
    public function setConfiguration(ConfigurationInterface $configuration)
297
    {
298
        $this->configuration = $configuration;
299
    }
300
301
    /**
302
     * Return's the system configuration.
303
     *
304
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
305
     */
306
    public function getConfiguration()
307
    {
308
        return $this->configuration;
309
    }
310
311
    /**
312
     * Set's the RegistryProcessor instance to handle the running threads.
313
     *
314
     * @param \TechDivision\Import\Services\RegistryProcessor $registryProcessor The registry processor instance
315
     *
316
     * @return void
317
     */
318
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
319
    {
320
        $this->registryProcessor = $registryProcessor;
321
    }
322
323
    /**
324
     * Return's the RegistryProcessor instance to handle the running threads.
325
     *
326
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
327
     */
328
    public function getRegistryProcessor()
329
    {
330
        return $this->registryProcessor;
331
    }
332
333
    /**
334
     * Set's the import processor instance.
335
     *
336
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
337
     *
338
     * @return void
339
     */
340
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
341
    {
342
        $this->importProcessor = $importProcessor;
343
    }
344
345
    /**
346
     * Return's the import processor instance.
347
     *
348
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
349
     */
350
    public function getImportProcessor()
351
    {
352
        return $this->importProcessor;
353
    }
354
355
    /**
356
     * The array with the system loggers.
357
     *
358
     * @param \Doctrine\Common\Collections\Collection $systemLoggers The system logger instances
359
     *
360
     * @return void
361
     */
362
    public function setSystemLoggers(Collection $systemLoggers)
363
    {
364
        $this->systemLoggers = $systemLoggers;
365
    }
366
367
    /**
368
     * Set's the plugin factory instance.
369
     *
370
     * @param \TechDivision\Import\Plugins\PluginFactoryInterface $pluginFactory The plugin factory instance
371
     *
372
     * @return void
373
     */
374
    public function setPluginFactory(PluginFactoryInterface $pluginFactory)
375
    {
376
        $this->pluginFactory = $pluginFactory;
377
    }
378
379
    /**
380
     * Return's the plugin factory instance.
381
     *
382
     * @return \TechDivision\Import\Plugins\PluginFactoryInterface The plugin factory instance
383
     */
384
    public function getPluginFactory()
385
    {
386
        return $this->pluginFactory;
387
    }
388
389
    /**
390
     * Return's the logger with the passed name, by default the system logger.
391
     *
392
     * @param string $name The name of the requested system logger
393
     *
394
     * @return LoggerInterface The logger instance
395
     * @throws \Exception Is thrown, if the requested logger is NOT available
396
     */
397
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
398
    {
399
400
        // query whether or not, the requested logger is available
401
        if (isset($this->systemLoggers[$name])) {
402
            return $this->systemLoggers[$name];
403
        }
404
405
        // throw an exception if the requested logger is NOT available
406
        throw new \Exception(
407
            sprintf(
408
                'The requested logger \'%s\' is not available',
409
                $name
410
            )
411
        );
412
    }
413
414
    /**
415
     * Query whether or not the system logger with the passed name is available.
416
     *
417
     * @param string $name The name of the requested system logger
418
     *
419
     * @return boolean TRUE if the logger with the passed name exists, else FALSE
420
     */
421
    public function hasSystemLogger($name = LoggerKeys::SYSTEM)
422
    {
423
        return isset($this->systemLoggers[$name]);
424
    }
425
426
    /**
427
     * Return's the array with the system logger instances.
428
     *
429
     * @return \Doctrine\Common\Collections\Collection The logger instance
430
     */
431
    public function getSystemLoggers()
432
    {
433
        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...
434
    }
435
436
    /**
437
     * Return's the unique serial for this import process.
438
     *
439
     * @return string The unique serial
440
     */
441
    public function getSerial()
442
    {
443
        return $this->serial;
444
    }
445
446
    /**
447
     * The shutdown handler to catch fatal errors.
448
     *
449
     * This method is need to make sure, that an existing PID file will be removed
450
     * if a fatal error has been triggered.
451
     *
452
     * @return void
453
     */
454
    public function shutdown()
455
    {
456
457
        // check if there was a fatal error caused shutdown
458
        if ($lastError = error_get_last()) {
459
            // initialize error type and message
460
            $type = 0;
461
            $message = '';
462
            // extract the last error values
463
            extract($lastError);
464
            // query whether we've a fatal/user error
465
            if ($type === E_ERROR || $type === E_USER_ERROR) {
466
                // clean-up the PID file
467
                $this->unlock();
468
                // log the fatal error message
469
                $this->log($message, LogLevel::ERROR);
470
            }
471
        }
472
    }
473
474
    /**
475
     * Persist the UUID of the actual import process to the PID file.
476
     *
477
     * @return void
478
     * @throws \Exception Is thrown, if the PID can not be locked or the PID can not be added
479
     * @throws \TechDivision\Import\Exceptions\ImportAlreadyRunningException Is thrown, if a import process is already running
480
     */
481
    public function lock()
482
    {
483
484
        // query whether or not, the PID has already been set
485
        if ($this->pid === $this->getSerial()) {
486
            return;
487
        }
488
489
        // if not, initialize the PID
490
        $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...
491
492
        // open the PID file
493
        $this->fh = fopen($filename = $this->getPidFilename(), 'a+');
494
495
        // try to lock the PID file exclusive
496
        if (!flock($this->fh, LOCK_EX|LOCK_NB)) {
497
            throw new ImportAlreadyRunningException(sprintf('PID file %s is already in use', $filename));
498
        }
499
500
        // append the PID to the PID file
501
        if (fwrite($this->fh, $this->pid . PHP_EOL) === false) {
502
            throw new \Exception(sprintf('Can\'t write PID %s to PID file %s', $this->pid, $filename));
503
        }
504
    }
505
506
    /**
507
     * Remove's the UUID of the actual import process from the PID file.
508
     *
509
     * @return void
510
     * @throws \Exception Is thrown, if the PID can not be removed
511
     */
512
    public function unlock()
513
    {
514
        try {
515
            // remove the PID from the PID file if set
516
            if ($this->pid === $this->getSerial() && is_resource($this->fh)) {
517
                // remove the PID from the file
518
                $this->removeLineFromFile($this->pid, $this->fh);
519
520
                // finally unlock/close the PID file
521
                flock($this->fh, LOCK_UN);
522
                fclose($this->fh);
523
524
                // if the PID file is empty, delete the file
525
                if (filesize($filename = $this->getPidFilename()) === 0) {
526
                    unlink($filename);
527
                }
528
            }
529
530
        } catch (FileNotFoundException $fnfe) {
531
            $this->getSystemLogger()->notice(sprintf('PID file %s doesn\'t exist', $this->getPidFilename()));
532
        } catch (LineNotFoundException $lnfe) {
533
            $this->getSystemLogger()->notice(sprintf('PID %s is can not be found in PID file %s', $this->pid, $this->getPidFilename()));
534
        } catch (\Exception $e) {
535
            throw new \Exception(sprintf('Can\'t remove PID %s from PID file %s', $this->pid, $this->getPidFilename()), null, $e);
536
        }
537
    }
538
539
    /**
540
     * Remove's the passed line from the file with the passed name.
541
     *
542
     * @param string   $line The line to be removed
543
     * @param resource $fh   The file handle of the file the line has to be removed
544
     *
545
     * @return void
546
     * @throws \Exception Is thrown, if the file doesn't exists, the line is not found or can not be removed
547
     */
548
    public function removeLineFromFile($line, $fh)
549
    {
550
551
        // initialize the array for the PIDs found in the PID file
552
        $lines = array();
553
554
        // initialize the flag if the line has been found
555
        $found = false;
556
557
        // rewind the file pointer
558
        rewind($fh);
559
560
        // read the lines with the PIDs from the PID file
561
        while (($buffer = fgets($fh, 4096)) !== false) {
562
            // remove the new line
563
            $buffer = trim($buffer);
564
            // if the line is the one to be removed, ignore the line
565
            if ($line === $buffer) {
566
                $found = true;
567
                continue;
568
            }
569
570
            // add the found PID to the array
571
            $lines[] = $buffer;
572
        }
573
574
        // query whether or not, we found the line
575
        if (!$found) {
576
            throw new LineNotFoundException(sprintf('Line %s can not be found', $line));
577
        }
578
579
        // empty the file and rewind the file pointer
580
        ftruncate($fh, 0);
581
        rewind($fh);
582
583
        // append the existing lines to the file
584
        foreach ($lines as $ln) {
585
            if (fwrite($fh, $ln . PHP_EOL) === false) {
586
                throw new \Exception(sprintf('Can\'t write %s to file', $ln));
587
            }
588
        }
589
    }
590
591
    /**
592
     * Process the given operation.
593
     *
594
     * @return void
595
     * @throws \Exception Is thrown if the operation can't be finished successfully
596
     */
597
    public function process()
598
    {
599
600
        try {
601
            // track the start time
602
            $startTime = microtime(true);
603
604
            // invoke the event that has to be fired before the application start's the transaction
605
            // (if single transaction mode has been activated)
606
            $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...
607
608
            // start the transaction, if single transaction mode has been configured
609
            if ($this->getConfiguration()->isSingleTransaction()) {
610
                $this->getImportProcessor()->getConnection()->beginTransaction();
611
            }
612
613
            // prepare the global data for the import process
614
            $this->setUp();
615
616
            // process the plugins defined in the configuration
617
            /** @var \TechDivision\Import\Configuration\PluginConfigurationInterface $pluginConfiguration */
618
            foreach ($this->getConfiguration()->getPlugins() as $pluginConfiguration) {
619
                // query whether or not the operation has been stopped
620
                if ($this->isStopped()) {
621
                    break;
622
                }
623
                // process the plugin if not
624
                $this->pluginFactory->createPlugin($pluginConfiguration)->process();
625
            }
626
627
            // tear down the  instance
628
            $this->tearDown();
629
630
            // commit the transaction, if single transation mode has been configured
631
            if ($this->getConfiguration()->isSingleTransaction()) {
632
                $this->getImportProcessor()->getConnection()->commit();
633
            }
634
635
            // track the time needed for the import in seconds
636
            $endTime = microtime(true) - $startTime;
637
638
            // log a message that import has been finished
639
            $this->log(
640
                sprintf(
641
                    'Successfully finished import with serial %s in %f s',
642
                    $this->getSerial(),
643
                    $endTime
644
                ),
645
                LogLevel::INFO
646
            );
647
648
            // invoke the event that has to be fired before the application has the transaction
649
            // committed successfully (if single transaction mode has been activated)
650
            $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...
651
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
687
        } catch (\Exception $e) {
688
            // tear down
689
            $this->tearDown();
690
691
            // rollback the transaction, if single transaction mode has been configured
692
            if ($this->getConfiguration()->isSingleTransaction()) {
693
                $this->getImportProcessor()->getConnection()->rollBack();
694
            }
695
696
            // invoke the event that has to be fired after the application rollbacked the
697
            // transaction (if single transaction mode has been activated)
698
            $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...
699
700
            // finally, if a PID has been set (because CSV files has been found),
701
            // remove it from the PID file to unlock the importer
702
            $this->unlock();
703
704
            // track the time needed for the import in seconds
705
            $endTime = microtime(true) - $startTime;
706
707
            // log a message that the file import failed
708
            foreach ($this->systemLoggers as $systemLogger) {
709
                $systemLogger->error($e->__toString());
710
            }
711
712
            // log a message that import has been finished
713
            $this->getSystemLogger()->info(
714
                sprintf(
715
                    'Can\'t finish import with serial %s in %f s',
716
                    $this->getSerial(),
717
                    $endTime
718
                )
719
            );
720
721
            // re-throw the exception
722
            throw $e;
723
        }
724
    }
725
726
    /**
727
     * Stop processing the operation.
728
     *
729
     * @param string $reason The reason why the operation has been stopped
730
     *
731
     * @return void
732
     */
733
    public function stop($reason)
734
    {
735
736
        // log a message that the operation has been stopped
737
        $this->log($reason, LogLevel::INFO);
738
739
        // stop processing the plugins by setting the flag to TRUE
740
        $this->stopped = true;
741
    }
742
743
    /**
744
     * Return's TRUE if the operation has been stopped, else FALSE.
745
     *
746
     * @return boolean TRUE if the process has been stopped, else FALSE
747
     */
748
    public function isStopped()
749
    {
750
        return $this->stopped;
751
    }
752
753
    /**
754
     * Gets a service.
755
     *
756
     * @param string $id The service identifier
757
     *
758
     * @return object The associated service
759
     *
760
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException When a circular reference is detected
761
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException          When the service is not defined
762
     */
763
    public function get($id)
764
    {
765
        return $this->getContainer()->get($id);
766
    }
767
768
    /**
769
     * Returns true if the container can return an entry for the given identifier.
770
     * Returns false otherwise.
771
     *
772
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
773
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
774
     *
775
     * @param string $id Identifier of the entry to look for.
776
     *
777
     * @return bool
778
     */
779
    public function has($id)
780
    {
781
        return $this->getContainer()->has($id);
782
    }
783
784
    /**
785
     * Lifecycle callback that will be inovked before the
786
     * import process has been started.
787
     *
788
     * @return void
789
     */
790
    protected function setUp()
791
    {
792
793
        // generate the serial for the new job
794
        $this->serial = Uuid::uuid4()->__toString();
795
796
        // write the TechDivision ANSI art icon to the console
797
        $this->log($this->ansiArt);
798
799
        // log the debug information, if debug mode is enabled
800
        if ($this->getConfiguration()->isDebugMode()) {
801
            // load the application from the DI container
802
            /** @var TechDivision\Import\App\Application $application */
803
            $application = $this->getContainer()->get(DependencyInjectionKeys::APPLICATION);
804
            // log the system's PHP configuration
805
            $this->log(sprintf('PHP version: %s', phpversion()), LogLevel::DEBUG);
806
            $this->log(sprintf('App version: %s', $application->getVersion()), LogLevel::DEBUG);
807
            $this->log('-------------------- Loaded Extensions -----------------------', LogLevel::DEBUG);
808
            $this->log(implode(', ', $loadedExtensions = get_loaded_extensions()), LogLevel::DEBUG);
809
            $this->log('--------------------------------------------------------------', LogLevel::DEBUG);
810
811
            // write a warning for low performance, if XDebug extension is activated
812
            if (in_array('xdebug', $loadedExtensions)) {
813
                $this->log('Low performance exptected, as result of enabled XDebug extension!', LogLevel::WARNING);
814
            }
815
        }
816
817
        // log a message that import has been started
818
        $this->log(
819
            sprintf(
820
                'Now start import with serial %s [%s => %s]',
821
                $this->getSerial(),
822
                $this->getConfiguration()->getEntityTypeCode(),
823
                $this->getConfiguration()->getOperationName()
824
            ),
825
            LogLevel::INFO
826
        );
827
828
        // initialize the status
829
        $status = array(
830
            RegistryKeys::STATUS => 1,
831
            RegistryKeys::BUNCHES => 0,
832
            RegistryKeys::SOURCE_DIRECTORY => $this->getConfiguration()->getSourceDir(),
833
            RegistryKeys::MISSING_OPTION_VALUES => array()
834
        );
835
836
        // append it to the registry
837
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
838
    }
839
840
    /**
841
     * Lifecycle callback that will be inovked after the
842
     * import process has been finished.
843
     *
844
     * @return void
845
     */
846
    protected function tearDown()
847
    {
848
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
849
    }
850
851
    /**
852
     * Simple method that writes the passed method the the console and the
853
     * system logger, if configured and a log level has been passed.
854
     *
855
     * @param string $msg      The message to log
856
     * @param string $logLevel The log level to use
857
     *
858
     * @return void
859
     */
860
    protected function log($msg, $logLevel = null)
861
    {
862
863
        // initialize the formatter helper
864
        $helper = new FormatterHelper();
865
866
        // map the log level to the console style
867
        $style = $this->mapLogLevelToStyle($logLevel);
868
869
        // format the message, according to the passed log level and write it to the console
870
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);
871
872
        // log the message if a log level has been passed
873
        $systemLogger = $this->getSystemLogger();
874
875
        if ($this->shouldBeLogged($systemLogger, $logLevel)) {
876
            $systemLogger->log($logLevel, $msg);
877
        }
878
    }
879
880
    /**
881
     * Check if the message should be logged.
882
     *
883
     * @param \Psr\Log\LoggerInterface $logger      The logger instance
884
     * @param string                   $logLevel    The log level to use
885
     *
886
     * @return bool
887
     */
888
    protected function shouldBeLogged(LoggerInterface $logger, $logLevel)
889
    {
890
        $loggerAvailable = $logLevel && $logger;
891
        $debugMode = $this->getConfiguration()->isDebugMode();
892
        $isCritical = $logLevel === LogLevel::CRITICAL;
893
894
        return $loggerAvailable && (!$debugMode || $isCritical);
895
    }
896
897
    /**
898
     * Map's the passed log level to a valid symfony console style.
899
     *
900
     * @param string $logLevel The log level to map
901
     *
902
     * @return string The apropriate symfony console style
903
     */
904
    protected function mapLogLevelToStyle($logLevel)
905
    {
906
907
        // query whether or not the log level is mapped
908
        if (isset($this->logLevelStyleMapping[$logLevel])) {
909
            return $this->logLevelStyleMapping[$logLevel];
910
        }
911
912
        // return the default style => info
913
        return Simple::DEFAULT_STYLE;
914
    }
915
916
    /**
917
     * Return's the PID filename to use.
918
     *
919
     * @return string The PID filename
920
     */
921
    protected function getPidFilename()
922
    {
923
        return $this->getConfiguration()->getPidFilename();
924
    }
925
}
926