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; |
|
|
|
|
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(); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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()) { |
|
|
|
|
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
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.