FileExecutor   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Test Coverage

Coverage 51.14%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 217
dl 0
loc 472
ccs 112
cts 219
cp 0.5114
rs 2
c 1
b 0
f 0
wmc 101

15 Methods

Rating   Name   Duplication   Size   Complexity  
B load_csv() 0 45 11
A isScalarReference() 0 3 1
B setDataReferences() 0 40 10
A exists() 0 25 6
A load() 0 13 3
B prepend() 0 31 10
A resolveReferencesInText() 0 3 1
B copy() 0 24 8
C save() 0 32 12
C setReferences() 0 49 13
B move() 0 24 8
A delete() 0 18 4
A execute() 0 17 4
A __construct() 0 3 1
B append() 0 27 9

How to fix   Complexity   

Complex Class

Complex classes like FileExecutor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileExecutor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use Kaliop\eZMigrationBundle\API\EmbeddedReferenceResolverBagInterface;
6
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
7
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
8
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
9
10
/**
11
 * @property EmbeddedReferenceResolverBagInterface $referenceResolver
12
 */
13
class FileExecutor extends AbstractExecutor
14
{
15
    use IgnorableStepExecutorTrait;
0 ignored issues
show
Bug introduced by
The trait Kaliop\eZMigrationBundle...orableStepExecutorTrait requires the property $dsl which is not provided by Kaliop\eZMigrationBundle...e\Executor\FileExecutor.
Loading history...
16
    use ReferenceSetterTrait;
17
    use NonScalarReferenceSetterTrait;
0 ignored issues
show
Bug introduced by
The trait Kaliop\eZMigrationBundle...larReferenceSetterTrait requires the property $dsl which is not provided by Kaliop\eZMigrationBundle...e\Executor\FileExecutor.
Loading history...
18
19
    protected $supportedStepTypes = array('file');
20
    protected $supportedActions = array('load', 'load_csv', 'save', 'copy', 'move', 'delete', 'append', 'prepend', 'exists');
21
22
    protected $scalarReferences = array('count');
23
24
    /**
25
     * @param EmbeddedReferenceResolverBagInterface $referenceResolver
26 149
     */
27
    public function __construct(EmbeddedReferenceResolverBagInterface $referenceResolver)
28 149
    {
29 149
        $this->referenceResolver = $referenceResolver;
30
    }
31
32
    /**
33
     * @param MigrationStep $step
34
     * @return mixed
35
     * @throws \Exception
36 3
     */
37
    public function execute(MigrationStep $step)
38 3
    {
39
        parent::execute($step);
40 3
41
        if (!isset($step->dsl['mode'])) {
42
            throw new InvalidStepDefinitionException("Invalid step definition: missing 'mode'");
43
        }
44 3
45
        $action = $step->dsl['mode'];
46 3
47
        if (!in_array($action, $this->supportedActions)) {
48
            throw new InvalidStepDefinitionException("Invalid step definition: value '$action' is not allowed for 'mode'");
49
        }
50 3
51
        $this->skipStepIfNeeded($step);
52 3
53
        return $action == 'load_csv' ? $this->$action($step) : $this->$action($step->dsl, $step->context);
54
    }
55
56
    /**
57
     * @param array $dsl
58
     * @param array $context
59
     * @return string
60
     * @throws \Exception
61 2
     */
62
    protected function load($dsl, $context)
63 2
    {
64
        if (!isset($dsl['file'])) {
65
            throw new InvalidStepDefinitionException("Can not load file: name missing");
66 2
        }
67 2
        $fileName = $this->resolveReference($dsl['file']);
68
        if (!file_exists($fileName)) {
69
            throw new MigrationBundleException("Can not load '$fileName': file missing");
70
        }
71 2
72
        $this->setReferences($fileName, $dsl);
73 2
74
        return file_get_contents($fileName);
75
    }
76
77
    /**
78
     * @param MigrationStep $step
79
     * @return string[][]
80
     * @throws InvalidStepDefinitionException
81
     * @throws \Kaliop\eZMigrationBundle\API\Exception\InvalidMatchResultsNumberException
82
     */
83
    protected function load_csv($step)
84
    {
85
        if (!isset($step->dsl['expect'])) {
86
            // for csv files, it makes sense that we expect them to have many rows
87
            $step = new MigrationStep(
88
                $step->type,
89
                array_merge($step->dsl, array('expect' => self::$EXPECT_MANY)),
90
                $step->context
91
            );
92
        }
93
94
        $dsl = $step->dsl;
95
96
        if (!isset($dsl['file'])) {
97
            throw new InvalidStepDefinitionException("Can not load file: name missing");
98
        }
99
        $fileName = $this->resolveReference($dsl['file']);
100
        if (!file_exists($fileName)) {
101
            throw new MigrationBundleException("Can not load '$fileName': file missing");
102
        }
103
104
        $separator = isset($dsl['separator']) ? $this->resolveReference($dsl['separator']) : ',';
105
        $enclosure = isset($dsl['enclosure']) ? $this->resolveReference($dsl['enclosure']) : '"';
106
        $escape = isset($dsl['escape']) ? $this->resolveReference($dsl['escape']) : '\\';
107
108
        $singleResult = ($this->expectedResultsType($step) == self::$RESULT_TYPE_SINGLE);
109
110
        $data = array();
111
        if (($handle = fopen($fileName, "r")) !== FALSE) {
112
            while (($row = fgetcsv($handle, 0, $separator, $enclosure, $escape)) !== FALSE) {
113
                $data[] = $row;
114
                if ($singleResult && count($data) > 1) {
115
                    break;
116
                }
117
            }
118
            fclose($handle);
119
        } else {
120
            throw new MigrationBundleException("Can not load '$fileName'");
121
        }
122
123
        $this->validateResultsCount($data, $step);
124
        $this->setDataReferences($data, $dsl, $singleResult);
125
126
        // NB: this is one of the very few places where we return a nested array...
127
        return $data;
128
    }
129
130
    /**
131
     * @param array $dsl
132
     * @param array $context
133
     * @return bool
134
     * @throws \Exception
135 3
     */
136
    protected function exists($dsl, $context)
137 3
    {
138
        if (!isset($dsl['file'])) {
139
            throw new InvalidStepDefinitionException("Can not check for existence of file: name missing");
140 3
        }
141
        $fileName = $this->resolveReference($dsl['file']);
142 3
143
        $exists = file_exists($fileName);
144 3
145 3
        if (array_key_exists('references', $dsl)) {
146 3
            foreach ($dsl['references'] as $key => $reference) {
147 3
                $reference = $this->parseReferenceDefinition($key, $reference);
148 3
                switch ($reference['attribute']) {
149 3
                    case 'exists':
150 3
                        $overwrite = false;
151 1
                        if (isset($reference['overwrite'])) {
152
                            $overwrite = $reference['overwrite'];
153 3
                        }
154 3
                        $this->addReference($reference['identifier'], $exists, $overwrite);
155
                        break;
156
                }
157
            }
158
        }
159 3
160
        return $exists;
161
    }
162
163
    /**
164
     * @param array $dsl
165
     * @param array $context
166
     * @return int
167
     * @throws \Exception
168 1
     */
169
    protected function save($dsl, $context)
170 1
    {
171
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
172
            throw new InvalidStepDefinitionException("Can not save file: name or body or template missing");
173
        }
174 1
175 1
        if (isset($dsl['body']) && is_string($dsl['body'])) {
176 1
            $contents = $this->resolveReferencesInText($dsl['body']);
177 1
        } elseif (isset($dsl['template']) && is_string($dsl['template'])) {
178
            $path = $this->resolveReference($dsl['template']);
179 1
            // we use the same logic as for the image/file fields in content: look up file 1st relative to the migration
180 1
            $template = dirname($context['path']) . '/templates/' . $path;
181
            if (!is_file($template)) {
182
                $template = $path;
183 1
            }
184
            $contents = $this->resolveReferencesInText(file_get_contents($template));
185
        } else {
186
            throw new InvalidStepDefinitionException("Can not save file: either body or template tag must be a string");
187
        }
188 1
189
        $fileName = $this->resolveReference($dsl['file']);
190 1
191 1
        $overwrite = isset($dsl['overwrite']) ? $this->resolveReference($dsl['overwrite']) : false;
192
        if (!$overwrite && file_exists($fileName)) {
193
            throw new MigrationBundleException("Can not save file '$fileName: file already exists");
194
        }
195 1
196
        $return = file_put_contents($fileName, $contents);
197 1
198
        $this->setReferences($fileName, $dsl);
199 1
200
        return $return;
201
    }
202
203
    /**
204
     * @param array $dsl
205
     * @param array $context
206
     * @return int
207
     * @throws \Exception
208 1
     */
209
    protected function append($dsl, $context)
210 1
    {
211
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
212
            throw new InvalidStepDefinitionException("Can not append to file: name or body or template missing");
213
        }
214 1
215 1
        if (isset($dsl['body']) && is_string($dsl['body'])) {
216
            $contents = $this->resolveReferencesInText($dsl['body']);
217
        } elseif (isset($dsl['template']) && is_string($dsl['template'])) {
218
            $path = $this->resolveReference($dsl['template']);
219
            // we use the same logic as for the image/file fields in content: look up file 1st relative to the migration
220
            $template = dirname($context['path']) . '/templates/' . $path;
221
            if (!is_file($template)) {
222
                $template = $path;
223
            }
224
            $contents = $this->resolveReferencesInText(file_get_contents($template));
225
        } else {
226
            throw new InvalidStepDefinitionException("Can not append to file: either body or template tag must be a string");
227
        }
228 1
229
        $fileName = $this->resolveReference($dsl['file']);
230 1
231
        $return = file_put_contents($fileName, $contents, FILE_APPEND);
232 1
233
        $this->setReferences($fileName, $dsl);
234 1
235
        return $return;
236
    }
237
238
    /**
239
     * @param array $dsl
240
     * @param array $context
241
     * @return int
242
     * @throws \Exception
243
     */
244
    protected function prepend($dsl, $context)
245
    {
246
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
247
            throw new InvalidStepDefinitionException("Can not prepend to file: name or body or template missing");
248
        }
249
250
        if (isset($dsl['body']) && is_string($dsl['body'])) {
251
            $contents = $this->resolveReferencesInText($dsl['body']);
252
        } elseif (isset($dsl['template']) && is_string($dsl['template'])) {
253
            $path = $this->resolveReference($dsl['template']);
254
            // we use the same logic as for the image/file fields in content: look up file 1st relative to the migration
255
            $template = dirname($context['path']) . '/templates/' . $path;
256
            if (!is_file($template)) {
257
                $template = $path;
258
            }
259
            $contents = $this->resolveReferencesInText(file_get_contents($template));
260
        } else {
261
            throw new InvalidStepDefinitionException("Can not append to file: either body or template tag must be a string");
262
        }
263
264
        $fileName = $this->resolveReference($dsl['file']);
265
266
        if (file_exists($fileName)) {
267
            $contents .= file_get_contents($fileName);
268
        }
269
270
        $return = file_put_contents($fileName, $contents);
271
272
        $this->setReferences($fileName, $dsl);
273
274
        return $return;
275
    }
276
277
    /**
278
     * @param array $dsl
279
     * @param array $context
280
     * @return true
281
     * @throws \Exception
282 1
     */
283
    protected function copy($dsl, $context)
284 1
    {
285
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
286
            throw new InvalidStepDefinitionException("Can not copy file: from or to missing");
287
        }
288 1
289 1
        $fileName = $this->resolveReference($dsl['from']);
290
        if (!file_exists($fileName)) {
291
            throw new MigrationBundleException("Can not copy file '$fileName': file missing");
292
        }
293 1
294
        $this->setReferences($fileName, $dsl);
295 1
296 1
        $to = $this->resolveReference($dsl['to']);
297 1
        $overwrite = isset($dsl['overwrite']) ? $this->resolveReference($dsl['overwrite']) : false;
298
        if (!$overwrite && file_exists($to)) {
299
            throw new MigrationBundleException("Can not copy file to '$to: file already exists");
300
        }
301 1
302
        if (!copy($fileName, $to)) {
303
            throw new MigrationBundleException("Can not copy file '$fileName' to '$to': operation failed");
304
        }
305 1
306
        return true;
307
    }
308
309
    /**
310
     * @param array $dsl
311
     * @param array $context
312
     * @return true
313
     * @throws \Exception
314 1
     */
315
    protected function move($dsl, $context)
316 1
    {
317
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
318
            throw new InvalidStepDefinitionException("Can not move file: from or to missing");
319
        }
320 1
321 1
        $fileName = $this->resolveReference($dsl['from']);
322
        if (!file_exists($fileName)) {
323
            throw new MigrationBundleException("Can not move file '$fileName': file missing");
324
        }
325 1
326
        $this->setReferences($fileName, $dsl);
327 1
328 1
        $to = $this->resolveReference($dsl['to']);
329 1
        $overwrite = isset($dsl['overwrite']) ? $this->resolveReference($dsl['overwrite']) : false;
330
        if (!$overwrite && file_exists($to)) {
331
            throw new MigrationBundleException("Can not move to '$to': file already exists");
332
        }
333 1
334
        if (!rename($fileName, $to)) {
335
            throw new MigrationBundleException("Can not move file '$fileName': operation failed");
336
        }
337 1
338
        return true;
339
    }
340
341
    /**
342
     * @param array $dsl
343
     * @param array $context
344
     * @return true
345
     * @throws \Exception
346 3
     */
347
    protected function delete($dsl, $context)
348 3
    {
349
        if (!isset($dsl['file'])) {
350
            throw new InvalidStepDefinitionException("Can not delete file: name missing");
351
        }
352 3
353 3
        $fileName = $this->resolveReference($dsl['file']);
354
        if (!file_exists($fileName)) {
355
            throw new MigrationBundleException("Can not move delete '$fileName': file missing");
356
        }
357 3
358
        $this->setReferences($fileName, $dsl);
359 3
360
        if (!unlink($fileName)) {
361
            throw new MigrationBundleException("Can not delete file '$fileName': operation failed");
362
        }
363 3
364
        return true;
365
    }
366
367
    /**
368
     * @param $fileName
369
     * @param $dsl
370
     * @return bool
371
     * @throws InvalidStepDefinitionException
372 3
     */
373
    protected function setReferences($fileName, $dsl)
374 3
    {
375 3
        if (!array_key_exists('references', $dsl) || !count($dsl['references'])) {
376
            return false;
377
        }
378 2
379 2
        clearstatcache(true, $fileName);
380
        $stats = stat($fileName);
381 2
382
        if (!$stats) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stats of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
383
            throw new MigrationBundleException("Can not set references for file '$fileName': stat failed");
384
        }
385 2
386 2
        foreach ($dsl['references'] as $key => $reference) {
387 2
            $reference = $this->parseReferenceDefinition($key, $reference);
388 2
            switch ($reference['attribute']) {
389 2
                case 'body':
390 2
                    $value = file_get_contents($fileName);
391 1
                    break;
392 1
                case 'size':
393 1
                    $value = $stats[7];
394 1
                    break;
395
                case 'uid':
396
                    $value = $stats[4];
397 1
                    break;
398
                case 'gid':
399
                    $value = $stats[5];
400 1
                    break;
401 1
                case 'atime':
402 1
                    $value = $stats[8];
403 1
                    break;
404
                case 'mtime':
405
                    $value = $stats[9];
406 1
                    break;
407 1
                case 'ctime':
408 1
                    $value = $stats[10];
409
                    break;
410
                default:
411
                    throw new InvalidStepDefinitionException('File executor does not support setting references for attribute ' . $reference['attribute']);
412
            }
413 2
414 2
            $overwrite = false;
415 1
            if (isset($reference['overwrite'])) {
416
                $overwrite = $reference['overwrite'];
417 2
            }
418
            $this->addReference($reference['identifier'], $value, $overwrite);
419
        }
420 2
421
        return true;
422
    }
423
424
    protected function setDataReferences($data, $dsl, $singleResult)
425
    {
426
        if (!array_key_exists('references', $dsl) || !count($step->dsl['references'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $step seems to be never defined.
Loading history...
427
            return false;
428
        }
429
430
        foreach ($dsl['references'] as $key => $reference) {
431
            $reference = $this->parseReferenceDefinition($key, $reference);
432
            switch ($reference['attribute']) {
433
                case 'count':
434
                    $value = count($data);
435
                    break;
436
                default:
437
                    if (strpos($reference['attribute'], 'column.') !== 0) {
438
                        throw new InvalidStepDefinitionException('File Executor does not support setting references for attribute ' . $reference['attribute']);
439
                    }
440
                    if (count($data)) {
441
                        $colNum = substr($reference['attribute'], 7);
442
                        if (!isset($data[0][$colNum])) {
443
                            /// @todo use a MigrationBundleException ?
444
                            throw new \InvalidArgumentException('File Executor does not support setting references for attribute ' . $reference['attribute']);
445
                        }
446
                        $value = array_column($data, $colNum);
447
                        if ($singleResult) {
448
                            $value = reset($value);
449
                        }
450
                    } else {
451
                        // we should validate the requested column name, but we can't...
452
                        $value = array();
453
                    }
454
            }
455
456
            $overwrite = false;
457
            if (isset($reference['overwrite'])) {
458
                $overwrite = $reference['overwrite'];
459
            }
460
            $this->addReference($reference['identifier'], $value, $overwrite);
461
        }
462
463
        return true;
464
    }
465
466
    /**
467
     * Replaces any references inside a string
468
     *
469
     * @param string $text
470
     * @return string
471
     * @throws \Exception
472 1
     */
473
    protected function resolveReferencesInText($text)
474 1
    {
475
        return $this->referenceResolver->ResolveEmbeddedReferences($text);
476
    }
477
478
    /**
479
     * @param array $referenceDefinition
480
     * @return bool
481
     */
482
    protected function isScalarReference($referenceDefinition)
483
    {
484
        return in_array($referenceDefinition['attribute'], $this->scalarReferences);
485
    }
486
}
487