Completed
Push — master ( 0d7df6...af5d39 )
by Gaetano
08:38
created

FileExecutor::save()   D

Complexity

Conditions 9
Paths 10

Size

Total Lines 27
Code Lines 16

Duplication

Lines 10
Ratio 37.04 %

Code Coverage

Tests 8
CRAP Score 9.648

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 10
loc 27
rs 4.909
ccs 8
cts 10
cp 0.8
cc 9
eloc 16
nc 10
nop 2
crap 9.648
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
6
use Kaliop\eZMigrationBundle\Core\ReferenceResolver\PrefixBasedResolverInterface;
7
8
class FileExecutor extends AbstractExecutor
9
{
10
    use IgnorableStepExecutorTrait;
11
12
    protected $supportedStepTypes = array('file');
13
    protected $supportedActions = array('load', 'save', 'copy', 'move', 'delete', 'append', 'prepend');
14
15
    /** @var PrefixBasedResolverInterface $referenceResolver */
16 73
    protected $referenceResolver;
17
18 73
    public function __construct(PrefixBasedResolverInterface $referenceResolver)
19 73
    {
20
        $this->referenceResolver = $referenceResolver;
21
    }
22
23
    /**
24
     * @param MigrationStep $step
25
     * @return mixed
26 2
     * @throws \Exception
27
     */
28 2 View Code Duplication
    public function execute(MigrationStep $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
29
    {
30 2
        parent::execute($step);
31
32
        if (!isset($step->dsl['mode'])) {
33
            throw new \Exception("Invalid step definition: missing 'mode'");
34 2
        }
35
36 2
        $action = $step->dsl['mode'];
37
38
        if (!in_array($action, $this->supportedActions)) {
39
            throw new \Exception("Invalid step definition: value '$action' is not allowed for 'mode'");
40 2
        }
41
42
        $this->skipStepIfNeeded($step);
43
44
        return $this->$action($step->dsl, $step->context);
45
    }
46
47
    /**
48
     * @param array $dsl
49 2
     * @param array $context
50
     * @return string
51 2
     * @throws \Exception
52
     */
53
    protected function load($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
54 2
    {
55 2
        if (!isset($dsl['file'])) {
56
            throw new \Exception("Can not load file: name missing");
57
        }
58
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
59 2
        if (!file_exists($fileName)) {
60
            throw new \Exception("Can not move load '$fileName': file missing");
61 2
        }
62
63
        $this->setReferences($fileName, $dsl);
64
65
        return file_get_contents($fileName);
66
    }
67
68
    /**
69
     * @param array $dsl
70 1
     * @param array $context
71
     * @return int
72 1
     * @throws \Exception
73
     */
74
    protected function save($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
75
    {
76 1 View Code Duplication
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
77 1
            throw new \Exception("Can not save file: name or body or template missing");
78
        }
79
80 View Code Duplication
        if (is_string($dsl['body'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
            $contents = $this->resolveReferencesInText($dsl['body']);
82 1
        } elseif (is_string($dsl['template'])) {
83
            $contents = $this->resolveReferencesInText(file_get_contents($this->resolveReferences($dsl['template'])));
0 ignored issues
show
Bug introduced by
The method resolveReferences() does not exist on Kaliop\eZMigrationBundle...e\Executor\FileExecutor. Did you maybe mean resolveReferencesInText()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
84 1
        } else {
85 1
            throw new \Exception("Can not save file: either body or template tag must be a string");
86
        }
87
88
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
89 1
90
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
91 1
        if (!$overwrite && file_exists($fileName)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $overwrite of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
92
            throw new \Exception("Can not save file '$fileName: file already exists");
93 1
        }
94
95
        $return = file_put_contents($fileName, $contents);
96
97
        $this->setReferences($fileName, $dsl);
98
99
        return $return;
100
    }
101
102 1
    /**
103
     * @param array $dsl
104 1
     * @param array $context
105
     * @return int
106
     * @throws \Exception
107
     */
108 1
    protected function append($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
109 1
    {
110 View Code Duplication
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
            throw new \Exception("Can not append to file: name or body or template missing");
112
        }
113
114 1 View Code Duplication
        if (is_string($dsl['body'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115
            $contents = $this->resolveReferencesInText($dsl['body']);
116 1
        } elseif (is_string($dsl['template'])) {
117
            $contents = $this->resolveReferencesInText(file_get_contents($this->resolveReferences($dsl['template'])));
0 ignored issues
show
Bug introduced by
The method resolveReferences() does not exist on Kaliop\eZMigrationBundle...e\Executor\FileExecutor. Did you maybe mean resolveReferencesInText()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
118 1
        } else {
119
            throw new \Exception("Can not append to file: either body or template tag must be a string");
120 1
        }
121
122
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
123
124
        $return = file_put_contents($fileName, $contents, FILE_APPEND);
125
126
        $this->setReferences($fileName, $dsl);
127
128
        return $return;
129 1
    }
130
131 1
    /**
132
     * @param array $dsl
133
     * @param array $context
134
     * @return int
135 1
     * @throws \Exception
136 1
     */
137
    protected function prepend($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
138
    {
139 View Code Duplication
        if (!isset($dsl['file']) || (!isset($dsl['body']) && !isset($dsl['template']))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140 1
            throw new \Exception("Can not prepend to file: name or body or template missing");
141
        }
142 1
143 1 View Code Duplication
        if (is_string($dsl['body'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144 1
            $contents = $this->resolveReferencesInText($dsl['body']);
145
        } elseif (is_string($dsl['template'])) {
146
            $contents = $this->resolveReferencesInText(file_get_contents($this->resolveReferences($dsl['template'])));
0 ignored issues
show
Bug introduced by
The method resolveReferences() does not exist on Kaliop\eZMigrationBundle...e\Executor\FileExecutor. Did you maybe mean resolveReferencesInText()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
147
        } else {
148 1
            throw new \Exception("Can not append to file: either body or template tag must be a string");
149
        }
150
151
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
152 1
153
        if (file_exists($fileName)) {
154
            $contents .= file_get_contents($fileName);
155
        }
156
157
        $return = file_put_contents($fileName, $contents);
158
159
        $this->setReferences($fileName, $dsl);
160
161 1
        return $return;
162
    }
163 1
164
    /**
165
     * @param array $dsl
166
     * @param array $context
167 1
     * @return true
168 1
     * @throws \Exception
169
     */
170 View Code Duplication
    protected function copy($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
    {
172 1
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
173
            throw new \Exception("Can not copy file: from or to missing");
174 1
        }
175 1
176 1
        $fileName = $this->referenceResolver->resolveReference($dsl['from']);
177
        if (!file_exists($fileName)) {
178
            throw new \Exception("Can not copy file '$fileName': file missing");
179
        }
180 1
181
        $this->setReferences($fileName, $dsl);
182
183
        $to = $this->referenceResolver->resolveReference($dsl['to']);
184 1
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
185
        if (!$overwrite && file_exists($to)) {
186
            throw new \Exception("Can not copy file to '$to: file already exists");
187
        }
188
189
        if (!copy($fileName, $to)) {
190
            throw new \Exception("Can not copy file '$fileName' to '$to': operation failed");
191
        }
192
193 1
        return true;
194
    }
195 1
196
    /**
197
     * @param array $dsl
198
     * @param array $context
199 1
     * @return true
200 1
     * @throws \Exception
201
     */
202 View Code Duplication
    protected function move($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204 1
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
205
            throw new \Exception("Can not move file: from or to missing");
206 1
        }
207
208
        $fileName = $this->referenceResolver->resolveReference($dsl['from']);
209
        if (!file_exists($fileName)) {
210 1
            throw new \Exception("Can not move file '$fileName': file missing");
211
        }
212
213 2
        $this->setReferences($fileName, $dsl);
214
215 2
        $to = $this->referenceResolver->resolveReference($dsl['to']);
216 1
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
217
        if (!$overwrite && file_exists($to)) {
218
            throw new \Exception("Can not move to '$to': file already exists");
219 2
        }
220 2
221
        if (!rename($fileName, $to)) {
222 2
            throw new \Exception("Can not move file '$fileName': operation failed");
223
        }
224
225
        return true;
226 2
    }
227 2
228 2
    /**
229 2
     * @param array $dsl
230 2
     * @param array $context
231 1
     * @return true
232 1
     * @throws \Exception
233 1
     */
234 1
    protected function delete($dsl, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236
        if (!isset($dsl['file'])) {
237 1
            throw new \Exception("Can not delete file: name missing");
238
        }
239
240 1
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
241 1
        if (!file_exists($fileName)) {
242 1
            throw new \Exception("Can not move delete '$fileName': file missing");
243 1
        }
244
245
        $this->setReferences($fileName, $dsl);
246 1
247 1
        if (!unlink($fileName)) {
248 1
            throw new \Exception("Can not delete file '$fileName': operation failed");
249
        }
250
251
        return true;
252
    }
253 2
254 2
    protected function setReferences($fileName, $dsl)
255
    {
256
        if (!array_key_exists('references', $dsl)) {
257 2
            return false;
258
        }
259
260 2
        clearstatcache(true, $fileName);
261
        $stats = stat($fileName);
262
263
        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...
264
            throw new \Exception("Can not set references for file '$fileName': stat failed");
265
        }
266
267
        foreach ($dsl['references'] as $reference) {
268
            switch ($reference['attribute']) {
269 1
                case 'body':
270
                    $value = file_get_contents($fileName);
271
                    break;
272 1
                case 'size':
273
                    $value = $stats[7];
274 1
                    break;
275
                case 'uid':
276 1
                    $value = $stats[4];
277
                    break;
278 1
                case 'gid':
279 1
                    $value = $stats[5];
280 1
                    break;
281 1
                case 'atime':
282
                    $value = $stats[8];
283
                    break;
284
                case 'mtime':
285 1
                    $value = $stats[9];
286
                    break;
287
                case 'ctime':
288
                    $value = $stats[10];
289
                    break;
290
                default:
291
                    throw new \InvalidArgumentException('File executor does not support setting references for attribute ' . $reference['attribute']);
292
            }
293
294
            $overwrite = false;
295
            if (isset($reference['overwrite'])) {
296
                $overwrite = $reference['overwrite'];
297
            }
298
            $this->referenceResolver->addReference($reference['identifier'], $value, $overwrite);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Kaliop\eZMigrationBundle...xBasedResolverInterface as the method addReference() does only exist in the following implementations of said interface: Kaliop\eZMigrationBundle...ver\ChainPrefixResolver, Kaliop\eZMigrationBundle...CustomReferenceResolver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
299
        }
300
301
        return true;
302
    }
303
304
    /**
305
     * Replaces any references inside a string
306
     *
307
     * @param string
308
     * @return string
309
     */
310 View Code Duplication
    protected function resolveReferencesInText($text)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
311
    {
312
        // we need to alter the regexp we get from the resolver, as it will be used to match parts of text, not the whole string
313
        $regexp = substr($this->referenceResolver->getRegexp(), 1, -1);
314
        // NB: here we assume that all regexp resolvers give us a regexp with a very specific format...
315
        $regexp = '/\[' . preg_replace(array('/^\^/'), array('', ''), $regexp) . '[^]]+\]/';
316
317
        $count = preg_match_all($regexp, $text, $matches);
318
        // $matches[0][] will have the matched full string eg.: [reference:example_reference]
319
        if ($count) {
320
            foreach ($matches[0] as $referenceIdentifier) {
321
                $reference = $this->referenceResolver->getReferenceValue(substr($referenceIdentifier, 1, -1));
322
                $text = str_replace($referenceIdentifier, $reference, $text);
323
            }
324
        }
325
326
        return $text;
327
    }
328
}
329