|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* TechDivision\Import\Subjects\AbstractSubject |
|
5
|
|
|
* |
|
6
|
|
|
* NOTICE OF LICENSE |
|
7
|
|
|
* |
|
8
|
|
|
* This source file is subject to the Open Software License (OSL 3.0) |
|
9
|
|
|
* that is available through the world-wide-web at this URL: |
|
10
|
|
|
* http://opensource.org/licenses/osl-3.0.php |
|
11
|
|
|
* |
|
12
|
|
|
* PHP version 5 |
|
13
|
|
|
* |
|
14
|
|
|
* @author Tim Wagner <[email protected]> |
|
15
|
|
|
* @copyright 2016 TechDivision GmbH <[email protected]> |
|
16
|
|
|
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
|
17
|
|
|
* @link https://github.com/techdivision/import |
|
18
|
|
|
* @link http://www.techdivision.com |
|
19
|
|
|
*/ |
|
20
|
|
|
|
|
21
|
|
|
namespace TechDivision\Import\Subjects; |
|
22
|
|
|
|
|
23
|
|
|
use Psr\Log\LoggerInterface; |
|
24
|
|
|
use League\Flysystem\Filesystem; |
|
25
|
|
|
use League\Flysystem\Adapter\Local; |
|
26
|
|
|
use League\Flysystem\FilesystemInterface; |
|
27
|
|
|
use Goodby\CSV\Import\Standard\Lexer; |
|
28
|
|
|
use Goodby\CSV\Import\Standard\LexerConfig; |
|
29
|
|
|
use Goodby\CSV\Import\Standard\Interpreter; |
|
30
|
|
|
use TechDivision\Import\Utils\MemberNames; |
|
31
|
|
|
use TechDivision\Import\Utils\RegistryKeys; |
|
32
|
|
|
use TechDivision\Import\Utils\ConfigurationKeys; |
|
33
|
|
|
use TechDivision\Import\Services\RegistryProcessor; |
|
34
|
|
|
use TechDivision\Import\Callbacks\CallbackVisitor; |
|
35
|
|
|
use TechDivision\Import\Callbacks\CallbackInterface; |
|
36
|
|
|
use TechDivision\Import\Observers\ObserverVisitor; |
|
37
|
|
|
use TechDivision\Import\Observers\ObserverInterface; |
|
38
|
|
|
use TechDivision\Import\Services\RegistryProcessorInterface; |
|
39
|
|
|
use TechDivision\Import\Configuration\SubjectConfigurationInterface; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* An abstract subject implementation. |
|
43
|
|
|
* |
|
44
|
|
|
* @author Tim Wagner <[email protected]> |
|
45
|
|
|
* @copyright 2016 TechDivision GmbH <[email protected]> |
|
46
|
|
|
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
|
47
|
|
|
* @link https://github.com/techdivision/import |
|
48
|
|
|
* @link http://www.techdivision.com |
|
49
|
|
|
*/ |
|
50
|
|
|
abstract class AbstractSubject implements SubjectInterface |
|
51
|
|
|
{ |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* The root directory for the virtual filesystem. |
|
55
|
|
|
* |
|
56
|
|
|
* @var string |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $rootDir; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* The system configuration. |
|
62
|
|
|
* |
|
63
|
|
|
* @var \TechDivision\Import\Configuration\SubjectConfigurationInterface |
|
64
|
|
|
*/ |
|
65
|
|
|
protected $configuration; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* The system logger implementation. |
|
69
|
|
|
* |
|
70
|
|
|
* @var \Psr\Log\LoggerInterface |
|
71
|
|
|
*/ |
|
72
|
|
|
protected $systemLogger; |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* The RegistryProcessor instance to handle running threads. |
|
76
|
|
|
* |
|
77
|
|
|
* @var \TechDivision\Import\Services\RegistryProcessorInterface |
|
78
|
|
|
*/ |
|
79
|
|
|
protected $registryProcessor; |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* The actions unique serial. |
|
83
|
|
|
* |
|
84
|
|
|
* @var string |
|
85
|
|
|
*/ |
|
86
|
|
|
protected $serial; |
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* The name of the file to be imported. |
|
90
|
|
|
* |
|
91
|
|
|
* @var string |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $filename; |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* Array with the subject's observers. |
|
97
|
|
|
* |
|
98
|
|
|
* @var array |
|
99
|
|
|
*/ |
|
100
|
|
|
protected $observers = array(); |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* Array with the subject's callbacks. |
|
104
|
|
|
* |
|
105
|
|
|
* @var array |
|
106
|
|
|
*/ |
|
107
|
|
|
protected $callbacks = array(); |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* The subject's callback mappings. |
|
111
|
|
|
* |
|
112
|
|
|
* @var array |
|
113
|
|
|
*/ |
|
114
|
|
|
protected $callbackMappings = array(); |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* Contain's the column names from the header line. |
|
118
|
|
|
* |
|
119
|
|
|
* @var array |
|
120
|
|
|
*/ |
|
121
|
|
|
protected $headers = array(); |
|
122
|
|
|
|
|
123
|
|
|
/** |
|
124
|
|
|
* The virtual filesystem instance. |
|
125
|
|
|
* |
|
126
|
|
|
* @var \League\Flysystem\FilesystemInterface |
|
127
|
|
|
*/ |
|
128
|
|
|
protected $filesystem; |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* The actual line number. |
|
132
|
|
|
* |
|
133
|
|
|
* @var integer |
|
134
|
|
|
*/ |
|
135
|
|
|
protected $lineNumber = 0; |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* The actual operation name. |
|
139
|
|
|
* |
|
140
|
|
|
* @var string |
|
141
|
|
|
*/ |
|
142
|
|
|
protected $operationName ; |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* The flag that stop's overserver execution on the actual row. |
|
146
|
|
|
* |
|
147
|
|
|
* @var boolean |
|
148
|
|
|
*/ |
|
149
|
|
|
protected $skipRow = false; |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* The available EAV attribute sets. |
|
153
|
|
|
* |
|
154
|
|
|
* @var array |
|
155
|
|
|
*/ |
|
156
|
|
|
protected $attributeSets = array(); |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* The available EAV attributes, grouped by their attribute set and the attribute set name as keys. |
|
160
|
|
|
* |
|
161
|
|
|
* @var array |
|
162
|
|
|
*/ |
|
163
|
|
|
protected $attributes = array(); |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* The attribute set of the entity that has to be created. |
|
167
|
|
|
* |
|
168
|
|
|
* @var array |
|
169
|
|
|
*/ |
|
170
|
|
|
protected $attributeSet = array(); |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* Mappings for attribute code => CSV column header. |
|
174
|
|
|
* |
|
175
|
|
|
* @var array |
|
176
|
|
|
*/ |
|
177
|
|
|
protected $headerMappings = array( |
|
178
|
|
|
'product_online' => 'status', |
|
179
|
|
|
'tax_class_name' => 'tax_class_id', |
|
180
|
|
|
'bundle_price_type' => 'price_type', |
|
181
|
|
|
'bundle_sku_type' => 'sku_type', |
|
182
|
|
|
'bundle_price_view' => 'price_view', |
|
183
|
|
|
'bundle_weight_type' => 'weight_type', |
|
184
|
|
|
'base_image' => 'image', |
|
185
|
|
|
'base_image_label' => 'image_label', |
|
186
|
|
|
'thumbnail_image' => 'thumbnail', |
|
187
|
|
|
'thumbnail_image_label'=> 'thumbnail_label', |
|
188
|
|
|
'bundle_shipment_type' => 'shipment_type' |
|
189
|
|
|
); |
|
190
|
|
|
|
|
191
|
|
|
/** |
|
192
|
|
|
* Initialize the subject instance. |
|
193
|
|
|
* |
|
194
|
|
|
* @param \Psr\Log\LoggerInterface $systemLogger The system logger instance |
|
195
|
|
|
* @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration instance |
|
196
|
|
|
* @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance |
|
197
|
|
|
*/ |
|
198
|
|
|
public function __construct( |
|
199
|
|
|
LoggerInterface $systemLogger, |
|
200
|
|
|
SubjectConfigurationInterface $configuration, |
|
201
|
|
|
RegistryProcessorInterface $registryProcessor |
|
202
|
|
|
) { |
|
203
|
|
|
$this->systemLogger = $systemLogger; |
|
204
|
|
|
$this->configuration = $configuration; |
|
205
|
|
|
$this->registryProcessor = $registryProcessor; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
/** |
|
209
|
|
|
* Stop's observer execution on the actual row. |
|
210
|
|
|
* |
|
211
|
|
|
* @return void |
|
212
|
|
|
*/ |
|
213
|
|
|
public function skipRow() |
|
214
|
|
|
{ |
|
215
|
|
|
$this->skipRow = true; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
/** |
|
219
|
|
|
* Return's the actual line number. |
|
220
|
|
|
* |
|
221
|
|
|
* @return integer The line number |
|
222
|
|
|
*/ |
|
223
|
|
|
public function getLineNumber() |
|
224
|
|
|
{ |
|
225
|
|
|
return $this->lineNumber; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* Return's the actual operation name. |
|
230
|
|
|
* |
|
231
|
|
|
* @return string |
|
232
|
|
|
*/ |
|
233
|
|
|
public function getOperationName() |
|
234
|
|
|
{ |
|
235
|
|
|
return $this->operationName; |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
/** |
|
239
|
|
|
* Set's the array containing header row. |
|
240
|
|
|
* |
|
241
|
|
|
* @param array $headers The array with the header row |
|
242
|
|
|
* |
|
243
|
|
|
* @return void |
|
244
|
|
|
*/ |
|
245
|
|
|
public function setHeaders(array $headers) |
|
246
|
|
|
{ |
|
247
|
|
|
$this->headers = $headers; |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
/** |
|
251
|
|
|
* Return's the array containing header row. |
|
252
|
|
|
* |
|
253
|
|
|
* @return array The array with the header row |
|
254
|
|
|
*/ |
|
255
|
|
|
public function getHeaders() |
|
256
|
|
|
{ |
|
257
|
|
|
return $this->headers; |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* Queries whether or not the header with the passed name is available. |
|
262
|
|
|
* |
|
263
|
|
|
* @param string $name The header name to query |
|
264
|
|
|
* |
|
265
|
|
|
* @return boolean TRUE if the header is available, else FALSE |
|
266
|
|
|
*/ |
|
267
|
|
|
public function hasHeader($name) |
|
268
|
|
|
{ |
|
269
|
|
|
return isset($this->headers[$name]); |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Return's the header value for the passed name. |
|
274
|
|
|
* |
|
275
|
|
|
* @param string $name The name of the header to return the value for |
|
276
|
|
|
* |
|
277
|
|
|
* @return mixed The header value |
|
278
|
|
|
* \InvalidArgumentException Is thrown, if the header with the passed name is NOT available |
|
279
|
|
|
*/ |
|
280
|
|
|
public function getHeader($name) |
|
281
|
|
|
{ |
|
282
|
|
|
|
|
283
|
|
|
// query whether or not, the header is available |
|
284
|
|
|
if (isset($this->headers[$name])) { |
|
285
|
|
|
return $this->headers[$name]; |
|
286
|
|
|
} |
|
287
|
|
|
|
|
288
|
|
|
// throw an exception, if not |
|
289
|
|
|
throw new \InvalidArgumentException(sprintf('Header %s is not available', $name)); |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* Add's the header with the passed name and position, if not NULL. |
|
294
|
|
|
* |
|
295
|
|
|
* @param string $name The header name to add |
|
296
|
|
|
* |
|
297
|
|
|
* @return integer The new headers position |
|
298
|
|
|
*/ |
|
299
|
|
|
public function addHeader($name) |
|
300
|
|
|
{ |
|
301
|
|
|
|
|
302
|
|
|
// add the header |
|
303
|
|
|
$this->headers[$name] = $position = sizeof($this->headers); |
|
304
|
|
|
|
|
305
|
|
|
// return the new header's position |
|
306
|
|
|
return $position; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
/** |
|
310
|
|
|
* Queries whether or not debug mode is enabled or not, default is TRUE. |
|
311
|
|
|
* |
|
312
|
|
|
* @return boolean TRUE if debug mode is enabled, else FALSE |
|
313
|
|
|
*/ |
|
314
|
|
|
public function isDebugMode() |
|
315
|
|
|
{ |
|
316
|
|
|
return $this->getConfiguration()->isDebugMode(); |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
/** |
|
320
|
|
|
* Return's the system configuration. |
|
321
|
|
|
* |
|
322
|
|
|
* @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The system configuration |
|
323
|
|
|
*/ |
|
324
|
|
|
public function getConfiguration() |
|
325
|
|
|
{ |
|
326
|
|
|
return $this->configuration; |
|
|
|
|
|
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* Return's the system logger. |
|
331
|
|
|
* |
|
332
|
|
|
* @return \Psr\Log\LoggerInterface The system logger instance |
|
333
|
|
|
*/ |
|
334
|
|
|
public function getSystemLogger() |
|
335
|
|
|
{ |
|
336
|
|
|
return $this->systemLogger; |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
/** |
|
340
|
|
|
* Set's root directory for the virtual filesystem. |
|
341
|
|
|
* |
|
342
|
|
|
* @param string $rootDir The root directory for the virtual filesystem |
|
343
|
|
|
* |
|
344
|
|
|
* @return void |
|
345
|
|
|
*/ |
|
346
|
|
|
public function setRootDir($rootDir) |
|
347
|
|
|
{ |
|
348
|
|
|
$this->rootDir = $rootDir; |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Return's the root directory for the virtual filesystem. |
|
353
|
|
|
* |
|
354
|
|
|
* @return string The root directory for the virtual filesystem |
|
355
|
|
|
*/ |
|
356
|
|
|
public function getRootDir() |
|
357
|
|
|
{ |
|
358
|
|
|
return $this->rootDir; |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
/** |
|
362
|
|
|
* Set's the virtual filesystem instance. |
|
363
|
|
|
* |
|
364
|
|
|
* @param \League\Flysystem\FilesystemInterface $filesystem The filesystem instance |
|
365
|
|
|
* |
|
366
|
|
|
* @return void |
|
367
|
|
|
*/ |
|
368
|
|
|
public function setFilesystem(FilesystemInterface $filesystem) |
|
369
|
|
|
{ |
|
370
|
|
|
$this->filesystem = $filesystem; |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
/** |
|
374
|
|
|
* Return's the virtual filesystem instance. |
|
375
|
|
|
* |
|
376
|
|
|
* @return \League\Flysystem\FilesystemInterface The filesystem instance |
|
377
|
|
|
*/ |
|
378
|
|
|
public function getFilesystem() |
|
379
|
|
|
{ |
|
380
|
|
|
return $this->filesystem; |
|
381
|
|
|
} |
|
382
|
|
|
|
|
383
|
|
|
/** |
|
384
|
|
|
* Return's the RegistryProcessor instance to handle the running threads. |
|
385
|
|
|
* |
|
386
|
|
|
* @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance |
|
387
|
|
|
*/ |
|
388
|
|
|
public function getRegistryProcessor() |
|
389
|
|
|
{ |
|
390
|
|
|
return $this->registryProcessor; |
|
391
|
|
|
} |
|
392
|
|
|
|
|
393
|
|
|
/** |
|
394
|
|
|
* Set's the unique serial for this import process. |
|
395
|
|
|
* |
|
396
|
|
|
* @param string $serial The unique serial |
|
397
|
|
|
* |
|
398
|
|
|
* @return void |
|
399
|
|
|
*/ |
|
400
|
|
|
public function setSerial($serial) |
|
401
|
|
|
{ |
|
402
|
|
|
$this->serial = $serial; |
|
403
|
|
|
} |
|
404
|
|
|
|
|
405
|
|
|
/** |
|
406
|
|
|
* Return's the unique serial for this import process. |
|
407
|
|
|
* |
|
408
|
|
|
* @return string The unique serial |
|
409
|
|
|
*/ |
|
410
|
|
|
public function getSerial() |
|
411
|
|
|
{ |
|
412
|
|
|
return $this->serial; |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
/** |
|
416
|
|
|
* Set's the name of the file to import |
|
417
|
|
|
* |
|
418
|
|
|
* @param string $filename The filename |
|
419
|
|
|
* |
|
420
|
|
|
* @return void |
|
421
|
|
|
*/ |
|
422
|
|
|
public function setFilename($filename) |
|
423
|
|
|
{ |
|
424
|
|
|
$this->filename = $filename; |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* Return's the name of the file to import. |
|
429
|
|
|
* |
|
430
|
|
|
* @return string The filename |
|
431
|
|
|
*/ |
|
432
|
|
|
public function getFilename() |
|
433
|
|
|
{ |
|
434
|
|
|
return $this->filename; |
|
435
|
|
|
} |
|
436
|
|
|
|
|
437
|
|
|
/** |
|
438
|
|
|
* Return's the source date format to use. |
|
439
|
|
|
* |
|
440
|
|
|
* @return string The source date format |
|
441
|
|
|
*/ |
|
442
|
|
|
public function getSourceDateFormat() |
|
443
|
|
|
{ |
|
444
|
|
|
return $this->getConfiguration()->getSourceDateFormat(); |
|
445
|
|
|
} |
|
446
|
|
|
|
|
447
|
|
|
/** |
|
448
|
|
|
* Return's the multiple field delimiter character to use, default value is comma (,). |
|
449
|
|
|
* |
|
450
|
|
|
* @return string The multiple field delimiter character |
|
451
|
|
|
*/ |
|
452
|
|
|
public function getMultipleFieldDelimiter() |
|
453
|
|
|
{ |
|
454
|
|
|
return $this->getConfiguration()->getMultipleFieldDelimiter(); |
|
|
|
|
|
|
455
|
|
|
} |
|
456
|
|
|
|
|
457
|
|
|
/** |
|
458
|
|
|
* Return's the initialized PDO connection. |
|
459
|
|
|
* |
|
460
|
|
|
* @return \PDO The initialized PDO connection |
|
461
|
|
|
*/ |
|
462
|
|
|
public function getConnection() |
|
463
|
|
|
{ |
|
464
|
|
|
return $this->getProductProcessor()->getConnection(); |
|
|
|
|
|
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
|
|
/** |
|
468
|
|
|
* Intializes the previously loaded global data for exactly one bunch. |
|
469
|
|
|
* |
|
470
|
|
|
* @return void |
|
471
|
|
|
* @see \Importer\Csv\Actions\ProductImportAction::prepare() |
|
472
|
|
|
*/ |
|
473
|
|
|
public function setUp() |
|
474
|
|
|
{ |
|
475
|
|
|
|
|
476
|
|
|
// load the status of the actual import |
|
477
|
|
|
$status = $this->getRegistryProcessor()->getAttribute($this->getSerial()); |
|
478
|
|
|
|
|
479
|
|
|
// load the global data we've prepared initially |
|
480
|
|
|
$this->attributes = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::EAV_ATTRIBUTES]; |
|
481
|
|
|
$this->attributeSets = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ATTRIBUTE_SETS]; |
|
482
|
|
|
|
|
483
|
|
|
// initialize the filesystems root directory |
|
484
|
|
|
$this->rootDir = $this->getConfiguration()->getParam(ConfigurationKeys::ROOT_DIRECTORY, getcwd()); |
|
485
|
|
|
|
|
486
|
|
|
// initialize the filesystem |
|
487
|
|
|
$this->filesystem = new Filesystem(new Local($this->getRootDir())); |
|
488
|
|
|
|
|
489
|
|
|
// initialize the operation name |
|
490
|
|
|
$this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName(); |
|
491
|
|
|
|
|
492
|
|
|
// initialize the callbacks/observers |
|
493
|
|
|
CallbackVisitor::get()->visit($this); |
|
494
|
|
|
ObserverVisitor::get()->visit($this); |
|
495
|
|
|
} |
|
496
|
|
|
|
|
497
|
|
|
/** |
|
498
|
|
|
* This method tries to resolve the passed path and returns it. If the path |
|
499
|
|
|
* is relative, the actual working directory will be prepended. |
|
500
|
|
|
* |
|
501
|
|
|
* @param string $path The path to be resolved |
|
502
|
|
|
* |
|
503
|
|
|
* @return string The resolved path |
|
504
|
|
|
* @throws \InvalidArgumentException Is thrown, if the path can not be resolved |
|
505
|
|
|
*/ |
|
506
|
|
|
public function resolvePath($path) |
|
507
|
|
|
{ |
|
508
|
|
|
// if we've an absolute path, return it immediately |
|
509
|
|
|
if ($this->getFilesystem()->has($path)) { |
|
510
|
|
|
return $path; |
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
|
|
// try to prepend the actual working directory, assuming we've a relative path |
|
514
|
|
|
if ($this->getFilesystem()->has($path = getcwd() . DIRECTORY_SEPARATOR . $path)) { |
|
515
|
|
|
return $path; |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
// throw an exception if the passed directory doesn't exists |
|
519
|
|
|
throw new \InvalidArgumentException( |
|
520
|
|
|
sprintf('Directory %s doesn\'t exist', $path) |
|
521
|
|
|
); |
|
522
|
|
|
} |
|
523
|
|
|
|
|
524
|
|
|
/** |
|
525
|
|
|
* Clean up the global data after importing the variants. |
|
526
|
|
|
* |
|
527
|
|
|
* @return void |
|
528
|
|
|
*/ |
|
529
|
|
|
public function tearDown() |
|
530
|
|
|
{ |
|
531
|
|
|
|
|
532
|
|
|
// load the registry processor |
|
533
|
|
|
$registryProcessor = $this->getRegistryProcessor(); |
|
534
|
|
|
|
|
535
|
|
|
// update the source directory for the next subject |
|
536
|
|
|
$registryProcessor->mergeAttributesRecursive( |
|
537
|
|
|
$this->getSerial(), |
|
538
|
|
|
array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir()) |
|
539
|
|
|
); |
|
540
|
|
|
|
|
541
|
|
|
// log a debug message with the new source directory |
|
542
|
|
|
$this->getSystemLogger()->debug( |
|
543
|
|
|
sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir()) |
|
544
|
|
|
); |
|
545
|
|
|
} |
|
546
|
|
|
|
|
547
|
|
|
/** |
|
548
|
|
|
* Return's the next source directory, which will be the target directory |
|
549
|
|
|
* of this subject, in most cases. |
|
550
|
|
|
* |
|
551
|
|
|
* @return string The new source directory |
|
552
|
|
|
*/ |
|
553
|
|
|
protected function getNewSourceDir() |
|
554
|
|
|
{ |
|
555
|
|
|
return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial()); |
|
556
|
|
|
} |
|
557
|
|
|
|
|
558
|
|
|
/** |
|
559
|
|
|
* Register the passed observer with the specific type. |
|
560
|
|
|
* |
|
561
|
|
|
* @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register |
|
562
|
|
|
* @param string $type The type to register the observer with |
|
563
|
|
|
* |
|
564
|
|
|
* @return void |
|
565
|
|
|
*/ |
|
566
|
|
|
public function registerObserver(ObserverInterface $observer, $type) |
|
567
|
|
|
{ |
|
568
|
|
|
|
|
569
|
|
|
// query whether or not the array with the callbacks for the |
|
570
|
|
|
// passed type has already been initialized, or not |
|
571
|
|
|
if (!isset($this->observers[$type])) { |
|
572
|
|
|
$this->observers[$type] = array(); |
|
573
|
|
|
} |
|
574
|
|
|
|
|
575
|
|
|
// append the callback with the instance of the passed type |
|
576
|
|
|
$this->observers[$type][] = $observer; |
|
577
|
|
|
} |
|
578
|
|
|
|
|
579
|
|
|
/** |
|
580
|
|
|
* Register the passed callback with the specific type. |
|
581
|
|
|
* |
|
582
|
|
|
* @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for |
|
583
|
|
|
* @param string $type The type to register the callback with |
|
584
|
|
|
* |
|
585
|
|
|
* @return void |
|
586
|
|
|
*/ |
|
587
|
|
|
public function registerCallback(CallbackInterface $callback, $type) |
|
588
|
|
|
{ |
|
589
|
|
|
|
|
590
|
|
|
// query whether or not the array with the callbacks for the |
|
591
|
|
|
// passed type has already been initialized, or not |
|
592
|
|
|
if (!isset($this->callbacks[$type])) { |
|
593
|
|
|
$this->callbacks[$type] = array(); |
|
594
|
|
|
} |
|
595
|
|
|
|
|
596
|
|
|
// append the callback with the instance of the passed type |
|
597
|
|
|
$this->callbacks[$type][] = $callback; |
|
598
|
|
|
} |
|
599
|
|
|
|
|
600
|
|
|
/** |
|
601
|
|
|
* Return's the array with callbacks for the passed type. |
|
602
|
|
|
* |
|
603
|
|
|
* @param string $type The type of the callbacks to return |
|
604
|
|
|
* |
|
605
|
|
|
* @return array The callbacks |
|
606
|
|
|
*/ |
|
607
|
|
|
public function getCallbacksByType($type) |
|
608
|
|
|
{ |
|
609
|
|
|
|
|
610
|
|
|
// initialize the array for the callbacks |
|
611
|
|
|
$callbacks = array(); |
|
612
|
|
|
|
|
613
|
|
|
// query whether or not callbacks for the type are available |
|
614
|
|
|
if (isset($this->callbacks[$type])) { |
|
615
|
|
|
$callbacks = $this->callbacks[$type]; |
|
616
|
|
|
} |
|
617
|
|
|
|
|
618
|
|
|
// return the array with the type's callbacks |
|
619
|
|
|
return $callbacks; |
|
620
|
|
|
} |
|
621
|
|
|
|
|
622
|
|
|
/** |
|
623
|
|
|
* Return's the array with the available observers. |
|
624
|
|
|
* |
|
625
|
|
|
* @return array The observers |
|
626
|
|
|
*/ |
|
627
|
|
|
public function getObservers() |
|
628
|
|
|
{ |
|
629
|
|
|
return $this->observers; |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
/** |
|
633
|
|
|
* Return's the array with the available callbacks. |
|
634
|
|
|
* |
|
635
|
|
|
* @return array The callbacks |
|
636
|
|
|
*/ |
|
637
|
|
|
public function getCallbacks() |
|
638
|
|
|
{ |
|
639
|
|
|
return $this->callbacks; |
|
640
|
|
|
} |
|
641
|
|
|
|
|
642
|
|
|
/** |
|
643
|
|
|
* Return's the callback mappings for this subject. |
|
644
|
|
|
* |
|
645
|
|
|
* @return array The array with the subject's callback mappings |
|
646
|
|
|
*/ |
|
647
|
|
|
public function getCallbackMappings() |
|
648
|
|
|
{ |
|
649
|
|
|
return $this->callbackMappings; |
|
650
|
|
|
} |
|
651
|
|
|
|
|
652
|
|
|
/** |
|
653
|
|
|
* Imports the content of the file with the passed filename. |
|
654
|
|
|
* |
|
655
|
|
|
* @param string $serial The unique process serial |
|
656
|
|
|
* @param string $filename The filename to process |
|
657
|
|
|
* |
|
658
|
|
|
* @return void |
|
659
|
|
|
* @throws \Exception Is thrown, if the import can't be processed |
|
660
|
|
|
*/ |
|
661
|
|
|
public function import($serial, $filename) |
|
662
|
|
|
{ |
|
663
|
|
|
|
|
664
|
|
|
try { |
|
665
|
|
|
// stop processing, if the filename doesn't match |
|
666
|
|
|
if (!$this->match($filename)) { |
|
667
|
|
|
return; |
|
668
|
|
|
} |
|
669
|
|
|
|
|
670
|
|
|
// load the system logger instance |
|
671
|
|
|
$systemLogger = $this->getSystemLogger(); |
|
672
|
|
|
|
|
673
|
|
|
// prepare the flag filenames |
|
674
|
|
|
$inProgressFilename = sprintf('%s.inProgress', $filename); |
|
675
|
|
|
$importedFilename = sprintf('%s.imported', $filename); |
|
676
|
|
|
$failedFilename = sprintf('%s.failed', $filename); |
|
677
|
|
|
|
|
678
|
|
|
// query whether or not the file has already been imported |
|
679
|
|
|
if (is_file($failedFilename) || |
|
680
|
|
|
is_file($importedFilename) || |
|
681
|
|
|
is_file($inProgressFilename) |
|
682
|
|
|
) { |
|
683
|
|
|
// log a debug message and exit |
|
684
|
|
|
$systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename)); |
|
685
|
|
|
return; |
|
686
|
|
|
} |
|
687
|
|
|
|
|
688
|
|
|
// flag file as in progress |
|
689
|
|
|
touch($inProgressFilename); |
|
690
|
|
|
|
|
691
|
|
|
// track the start time |
|
692
|
|
|
$startTime = microtime(true); |
|
693
|
|
|
|
|
694
|
|
|
// initialize serial and filename |
|
695
|
|
|
$this->setSerial($serial); |
|
696
|
|
|
$this->setFilename($filename); |
|
697
|
|
|
|
|
698
|
|
|
// load the system logger |
|
699
|
|
|
$systemLogger = $this->getSystemLogger(); |
|
700
|
|
|
|
|
701
|
|
|
// initialize the global global data to import a bunch |
|
702
|
|
|
$this->setUp(); |
|
703
|
|
|
|
|
704
|
|
|
// initialize the lexer instance itself |
|
705
|
|
|
$lexer = new Lexer($this->getLexerConfig()); |
|
706
|
|
|
|
|
707
|
|
|
// initialize the interpreter |
|
708
|
|
|
$interpreter = new Interpreter(); |
|
709
|
|
|
$interpreter->addObserver(array($this, 'importRow')); |
|
710
|
|
|
|
|
711
|
|
|
// query whether or not we want to use the strict mode |
|
712
|
|
|
if (!$this->getConfiguration()->isStrictMode()) { |
|
713
|
|
|
$interpreter->unstrict(); |
|
714
|
|
|
} |
|
715
|
|
|
|
|
716
|
|
|
// log a message that the file has to be imported |
|
717
|
|
|
$systemLogger->debug(sprintf('Now start importing file %s', $filename)); |
|
718
|
|
|
|
|
719
|
|
|
// parse the CSV file to be imported |
|
720
|
|
|
$lexer->parse($filename, $interpreter); |
|
721
|
|
|
|
|
722
|
|
|
// track the time needed for the import in seconds |
|
723
|
|
|
$endTime = microtime(true) - $startTime; |
|
724
|
|
|
|
|
725
|
|
|
// clean up the data after importing the bunch |
|
726
|
|
|
$this->tearDown(); |
|
727
|
|
|
|
|
728
|
|
|
// log a message that the file has successfully been imported |
|
729
|
|
|
$systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime)); |
|
730
|
|
|
|
|
731
|
|
|
// rename flag file, because import has been successfull |
|
732
|
|
|
rename($inProgressFilename, $importedFilename); |
|
733
|
|
|
|
|
|
|
|
|
|
734
|
|
|
} catch (\Exception $e) { |
|
735
|
|
|
// rename the flag file, because import failed and write the stack trace |
|
736
|
|
|
rename($inProgressFilename, $failedFilename); |
|
|
|
|
|
|
737
|
|
|
file_put_contents($failedFilename, $e->__toString()); |
|
738
|
|
|
|
|
739
|
|
|
// clean up the data after importing the bunch |
|
740
|
|
|
$this->tearDown(); |
|
741
|
|
|
|
|
742
|
|
|
// re-throw the exception |
|
743
|
|
|
throw $e; |
|
744
|
|
|
} |
|
745
|
|
|
} |
|
746
|
|
|
|
|
747
|
|
|
/** |
|
748
|
|
|
* This method queries whether or not the passed filename matches |
|
749
|
|
|
* the pattern, based on the subjects configured prefix. |
|
750
|
|
|
* |
|
751
|
|
|
* @param string $filename The filename to match |
|
752
|
|
|
* |
|
753
|
|
|
* @return boolean TRUE if the filename matches, else FALSE |
|
754
|
|
|
*/ |
|
755
|
|
|
protected function match($filename) |
|
756
|
|
|
{ |
|
757
|
|
|
|
|
758
|
|
|
// prepare the pattern to query whether the file has to be processed or not |
|
759
|
|
|
$pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix()); |
|
760
|
|
|
|
|
761
|
|
|
// stop processing, if the filename doesn't match |
|
762
|
|
|
return (boolean) preg_match($pattern, $filename); |
|
763
|
|
|
} |
|
764
|
|
|
|
|
765
|
|
|
/** |
|
766
|
|
|
* Initialize and return the lexer configuration. |
|
767
|
|
|
* |
|
768
|
|
|
* @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration |
|
769
|
|
|
*/ |
|
770
|
|
|
protected function getLexerConfig() |
|
771
|
|
|
{ |
|
772
|
|
|
|
|
773
|
|
|
// initialize the lexer configuration |
|
774
|
|
|
$config = new LexerConfig(); |
|
775
|
|
|
|
|
776
|
|
|
// query whether or not a delimiter character has been configured |
|
777
|
|
|
if ($delimiter = $this->getConfiguration()->getDelimiter()) { |
|
778
|
|
|
$config->setDelimiter($delimiter); |
|
779
|
|
|
} |
|
780
|
|
|
|
|
781
|
|
|
// query whether or not a custom escape character has been configured |
|
782
|
|
|
if ($escape = $this->getConfiguration()->getEscape()) { |
|
783
|
|
|
$config->setEscape($escape); |
|
784
|
|
|
} |
|
785
|
|
|
|
|
786
|
|
|
// query whether or not a custom enclosure character has been configured |
|
787
|
|
|
if ($enclosure = $this->getConfiguration()->getEnclosure()) { |
|
788
|
|
|
$config->setEnclosure($enclosure); |
|
789
|
|
|
} |
|
790
|
|
|
|
|
791
|
|
|
// query whether or not a custom source charset has been configured |
|
792
|
|
|
if ($fromCharset = $this->getConfiguration()->getFromCharset()) { |
|
793
|
|
|
$config->setFromCharset($fromCharset); |
|
794
|
|
|
} |
|
795
|
|
|
|
|
796
|
|
|
// query whether or not a custom target charset has been configured |
|
797
|
|
|
if ($toCharset = $this->getConfiguration()->getToCharset()) { |
|
798
|
|
|
$config->setToCharset($toCharset); |
|
799
|
|
|
} |
|
800
|
|
|
|
|
801
|
|
|
// return the lexer configuratio |
|
802
|
|
|
return $config; |
|
803
|
|
|
} |
|
804
|
|
|
|
|
805
|
|
|
/** |
|
806
|
|
|
* Imports the passed row into the database. |
|
807
|
|
|
* |
|
808
|
|
|
* If the import failed, the exception will be catched and logged, |
|
809
|
|
|
* but the import process will be continued. |
|
810
|
|
|
* |
|
811
|
|
|
* @param array $row The row with the data to be imported |
|
812
|
|
|
* |
|
813
|
|
|
* @return void |
|
814
|
|
|
*/ |
|
815
|
|
|
public function importRow(array $row) |
|
816
|
|
|
{ |
|
817
|
|
|
|
|
818
|
|
|
// raise the line number and reset the skip row flag |
|
819
|
|
|
$this->lineNumber++; |
|
820
|
|
|
$this->skipRow = false; |
|
821
|
|
|
|
|
822
|
|
|
// initialize the headers with the columns from the first line |
|
823
|
|
|
if (sizeof($this->headers) === 0) { |
|
824
|
|
|
foreach ($row as $value => $key) { |
|
825
|
|
|
$this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value; |
|
826
|
|
|
} |
|
827
|
|
|
return; |
|
828
|
|
|
} |
|
829
|
|
|
|
|
830
|
|
|
// process the observers |
|
831
|
|
|
foreach ($this->getObservers() as $observers) { |
|
832
|
|
|
// invoke the pre-import/import and post-import observers |
|
833
|
|
|
foreach ($observers as $observer) { |
|
834
|
|
|
// query whether or not we have to skip the row |
|
835
|
|
|
if ($this->skipRow) { |
|
836
|
|
|
break; |
|
837
|
|
|
} |
|
838
|
|
|
// if not, process the next observer |
|
839
|
|
|
if ($observer instanceof ObserverInterface) { |
|
840
|
|
|
$row = $observer->handle($row); |
|
|
|
|
|
|
841
|
|
|
} |
|
842
|
|
|
} |
|
843
|
|
|
} |
|
844
|
|
|
|
|
845
|
|
|
// log a debug message with the actual line nr/file information |
|
846
|
|
|
$this->getSystemLogger()->debug( |
|
847
|
|
|
sprintf( |
|
848
|
|
|
'Successfully processed row (operation: %s) in file %s on line %d', |
|
849
|
|
|
$this->operationName, |
|
850
|
|
|
$this->filename, |
|
851
|
|
|
$this->lineNumber |
|
852
|
|
|
) |
|
853
|
|
|
); |
|
854
|
|
|
} |
|
855
|
|
|
|
|
856
|
|
|
/** |
|
857
|
|
|
* Map the passed attribute code, if a header mapping exists and return the |
|
858
|
|
|
* mapped mapping. |
|
859
|
|
|
* |
|
860
|
|
|
* @param string $attributeCode The attribute code to map |
|
861
|
|
|
* |
|
862
|
|
|
* @return string The mapped attribute code, or the original one |
|
863
|
|
|
*/ |
|
864
|
|
|
public function mapAttributeCodeByHeaderMapping($attributeCode) |
|
865
|
|
|
{ |
|
866
|
|
|
|
|
867
|
|
|
// query weather or not we've a mapping, if yes, map the attribute code |
|
868
|
|
|
if (isset($this->headerMappings[$attributeCode])) { |
|
869
|
|
|
$attributeCode = $this->headerMappings[$attributeCode]; |
|
870
|
|
|
} |
|
871
|
|
|
|
|
872
|
|
|
// return the (mapped) attribute code |
|
873
|
|
|
return $attributeCode; |
|
874
|
|
|
} |
|
875
|
|
|
|
|
876
|
|
|
/** |
|
877
|
|
|
* Queries whether or not that the subject needs an OK file to be processed. |
|
878
|
|
|
* |
|
879
|
|
|
* @return boolean TRUE if the subject needs an OK file, else FALSE |
|
880
|
|
|
*/ |
|
881
|
|
|
public function isOkFileNeeded() |
|
882
|
|
|
{ |
|
883
|
|
|
return $this->getConfiguration()->isOkFileNeeded(); |
|
884
|
|
|
} |
|
885
|
|
|
|
|
886
|
|
|
/** |
|
887
|
|
|
* Return's the entity type code to be used. |
|
888
|
|
|
* |
|
889
|
|
|
* @return string The entity type code to be used |
|
890
|
|
|
*/ |
|
891
|
|
|
public function getEntityTypeCode() |
|
892
|
|
|
{ |
|
893
|
|
|
return $this->getConfiguration()->getConfiguration()->getEntityTypeCode(); |
|
894
|
|
|
} |
|
895
|
|
|
|
|
896
|
|
|
/** |
|
897
|
|
|
* Set's the attribute set of the product that has to be created. |
|
898
|
|
|
* |
|
899
|
|
|
* @param array $attributeSet The attribute set |
|
900
|
|
|
* |
|
901
|
|
|
* @return void |
|
902
|
|
|
*/ |
|
903
|
|
|
public function setAttributeSet(array $attributeSet) |
|
904
|
|
|
{ |
|
905
|
|
|
$this->attributeSet = $attributeSet; |
|
906
|
|
|
} |
|
907
|
|
|
|
|
908
|
|
|
/** |
|
909
|
|
|
* Return's the attribute set of the product that has to be created. |
|
910
|
|
|
* |
|
911
|
|
|
* @return array The attribute set |
|
912
|
|
|
*/ |
|
913
|
|
|
public function getAttributeSet() |
|
914
|
|
|
{ |
|
915
|
|
|
return $this->attributeSet; |
|
916
|
|
|
} |
|
917
|
|
|
|
|
918
|
|
|
/** |
|
919
|
|
|
* Return's the attribute set with the passed attribute set name. |
|
920
|
|
|
* |
|
921
|
|
|
* @param string $attributeSetName The name of the requested attribute set |
|
922
|
|
|
* |
|
923
|
|
|
* @return array The attribute set data |
|
924
|
|
|
* @throws \Exception Is thrown, if the attribute set with the passed name is not available |
|
925
|
|
|
*/ |
|
926
|
|
View Code Duplication |
public function getAttributeSetByAttributeSetName($attributeSetName) |
|
|
|
|
|
|
927
|
|
|
{ |
|
928
|
|
|
|
|
929
|
|
|
// query whether or not attribute sets for the actualy entity type code are available |
|
930
|
|
|
if (isset($this->attributeSets[$entityTypeCode = $this->getEntityTypeCode()])) { |
|
931
|
|
|
// load the attribute sets for the actualy entity type code |
|
932
|
|
|
$attributSets = $this->attributeSets[$entityTypeCode]; |
|
933
|
|
|
|
|
934
|
|
|
// query whether or not, the requested attribute set is available |
|
935
|
|
|
if (isset($attributSets[$attributeSetName])) { |
|
936
|
|
|
return $attributSets[$attributeSetName]; |
|
937
|
|
|
} |
|
938
|
|
|
} |
|
939
|
|
|
|
|
940
|
|
|
// throw an exception, if not |
|
941
|
|
|
throw new \Exception( |
|
942
|
|
|
sprintf( |
|
943
|
|
|
'Found invalid attribute set name %s in file %s on line %d', |
|
944
|
|
|
$attributeSetName, |
|
945
|
|
|
$this->getFilename(), |
|
946
|
|
|
$this->getLineNumber() |
|
947
|
|
|
) |
|
948
|
|
|
); |
|
949
|
|
|
} |
|
950
|
|
|
|
|
951
|
|
|
/** |
|
952
|
|
|
* Return's the attributes for the attribute set of the product that has to be created. |
|
953
|
|
|
* |
|
954
|
|
|
* @return array The attributes |
|
955
|
|
|
* @throws \Exception Is thrown if the attributes for the actual attribute set are not available |
|
956
|
|
|
*/ |
|
957
|
|
View Code Duplication |
public function getAttributes() |
|
|
|
|
|
|
958
|
|
|
{ |
|
959
|
|
|
|
|
960
|
|
|
// query whether or not, the requested EAV attributes are available |
|
961
|
|
|
if (isset($this->attributes[$entityTypeCode = $this->getEntityTypeCode()])) { |
|
962
|
|
|
// load the attributes for the entity type code |
|
963
|
|
|
$attributes = $this->attributes[$entityTypeCode]; |
|
964
|
|
|
|
|
965
|
|
|
// query whether or not attributes for the actual attribute set name |
|
966
|
|
|
if (isset($attributes[$attributeSetName = $this->attributeSet[MemberNames::ATTRIBUTE_SET_NAME]])) { |
|
967
|
|
|
return $attributes[$attributeSetName]; |
|
968
|
|
|
} |
|
969
|
|
|
} |
|
970
|
|
|
|
|
971
|
|
|
// throw an exception, if not |
|
972
|
|
|
throw new \Exception( |
|
973
|
|
|
sprintf( |
|
974
|
|
|
'Found invalid attribute set name "%s" in file %s on line %d', |
|
975
|
|
|
$attributeSetName, |
|
976
|
|
|
$this->getFilename(), |
|
977
|
|
|
$this->getLineNumber() |
|
978
|
|
|
) |
|
979
|
|
|
); |
|
980
|
|
|
} |
|
981
|
|
|
|
|
982
|
|
|
/** |
|
983
|
|
|
* Return's the EAV attribute with the passed attribute code. |
|
984
|
|
|
* |
|
985
|
|
|
* @param string $attributeCode The attribute code |
|
986
|
|
|
* |
|
987
|
|
|
* @return array The array with the EAV attribute |
|
988
|
|
|
* @throws \Exception Is thrown if the attribute with the passed code is not available |
|
989
|
|
|
*/ |
|
990
|
|
|
public function getEavAttributeByAttributeCode($attributeCode) |
|
991
|
|
|
{ |
|
992
|
|
|
|
|
993
|
|
|
// load the attributes |
|
994
|
|
|
$attributes = $this->getAttributes(); |
|
995
|
|
|
|
|
996
|
|
|
// query whether or not the attribute exists |
|
997
|
|
|
if (isset($attributes[$attributeCode])) { |
|
998
|
|
|
return $attributes[$attributeCode]; |
|
999
|
|
|
} |
|
1000
|
|
|
|
|
1001
|
|
|
// throw an exception if the requested attribute is not available |
|
1002
|
|
|
throw new \Exception( |
|
1003
|
|
|
sprintf( |
|
1004
|
|
|
'Can\'t load attribute with code "%s" in file %s and line %d', |
|
1005
|
|
|
$attributeCode, |
|
1006
|
|
|
$this->getFilename(), |
|
1007
|
|
|
$this->getLineNumber() |
|
1008
|
|
|
) |
|
1009
|
|
|
); |
|
1010
|
|
|
} |
|
1011
|
|
|
} |
|
1012
|
|
|
|
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_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.