Completed
Push — master ( 1bde99...439794 )
by Tim
02:32 queued 56s
created

Simple::setEmitter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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