Completed
Push — master ( 33df0c...bb62b8 )
by Gaetano
06:26
created

FileExecutor::delete()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 2
crap 4
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', 'exists');
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 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 string
72 1
     * @throws \Exception
73
     */
74
    protected function exists($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
        if (!isset($dsl['file'])) {
77 1
            throw new \Exception("Can not check for existence of file: name missing");
78
        }
79
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
80
81
        $exists = file_exists($fileName);
82 1
83
        if (array_key_exists('references', $dsl)) {
84 1
            foreach ($dsl['references'] as $reference) {
85 1
                switch ($reference['attribute']) {
86
                    case 'exists':
87
                        $overwrite = false;
88
                        if (isset($reference['overwrite'])) {
89 1
                            $overwrite = $reference['overwrite'];
90
                        }
91 1
                        $this->referenceResolver->addReference($reference['identifier'], $exists, $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...
92
                        break;
93 1
                }
94
            }
95
        }
96
97
        return $exists;
98
    }
99
100
    /**
101
     * @param array $dsl
102 1
     * @param array $context
103
     * @return int
104 1
     * @throws \Exception
105
     */
106
    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...
107
    {
108 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...
109 1
            throw new \Exception("Can not save file: name or body or template missing");
110
        }
111
112 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...
113
            $contents = $this->resolveReferencesInText($dsl['body']);
114 1
        } elseif (is_string($dsl['template'])) {
115
            $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...
116 1
        } else {
117
            throw new \Exception("Can not save file: either body or template tag must be a string");
118 1
        }
119
120 1
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
121
122
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
123
        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...
124
            throw new \Exception("Can not save file '$fileName: file already exists");
125
        }
126
127
        $return = file_put_contents($fileName, $contents);
128
129 1
        $this->setReferences($fileName, $dsl);
130
131 1
        return $return;
132
    }
133
134
    /**
135 1
     * @param array $dsl
136 1
     * @param array $context
137
     * @return int
138
     * @throws \Exception
139
     */
140 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...
141
    {
142 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...
143 1
            throw new \Exception("Can not append to file: name or body or template missing");
144 1
        }
145
146 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...
147
            $contents = $this->resolveReferencesInText($dsl['body']);
148 1
        } elseif (is_string($dsl['template'])) {
149
            $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...
150
        } else {
151
            throw new \Exception("Can not append to file: either body or template tag must be a string");
152 1
        }
153
154
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
155
156
        $return = file_put_contents($fileName, $contents, FILE_APPEND);
157
158
        $this->setReferences($fileName, $dsl);
159
160
        return $return;
161 1
    }
162
163 1
    /**
164
     * @param array $dsl
165
     * @param array $context
166
     * @return int
167 1
     * @throws \Exception
168 1
     */
169
    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...
170
    {
171 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...
172 1
            throw new \Exception("Can not prepend to file: name or body or template missing");
173
        }
174 1
175 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...
176 1
            $contents = $this->resolveReferencesInText($dsl['body']);
177
        } elseif (is_string($dsl['template'])) {
178
            $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...
179
        } else {
180 1
            throw new \Exception("Can not append to file: either body or template tag must be a string");
181
        }
182
183
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
184 1
185
        if (file_exists($fileName)) {
186
            $contents .= file_get_contents($fileName);
187
        }
188
189
        $return = file_put_contents($fileName, $contents);
190
191
        $this->setReferences($fileName, $dsl);
192
193 1
        return $return;
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 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...
203
    {
204 1
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
205
            throw new \Exception("Can not copy 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 copy 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 copy file to '$to: file already exists");
219 2
        }
220 2
221
        if (!copy($fileName, $to)) {
222 2
            throw new \Exception("Can not copy file '$fileName' to '$to': 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 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...
235
    {
236
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
237 1
            throw new \Exception("Can not move file: from or to missing");
238
        }
239
240 1
        $fileName = $this->referenceResolver->resolveReference($dsl['from']);
241 1
        if (!file_exists($fileName)) {
242 1
            throw new \Exception("Can not move file '$fileName': file missing");
243 1
        }
244
245
        $this->setReferences($fileName, $dsl);
246 1
247 1
        $to = $this->referenceResolver->resolveReference($dsl['to']);
248 1
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
249
        if (!$overwrite && file_exists($to)) {
250
            throw new \Exception("Can not move to '$to': file already exists");
251
        }
252
253 2
        if (!rename($fileName, $to)) {
254 2
            throw new \Exception("Can not move file '$fileName': operation failed");
255
        }
256
257 2
        return true;
258
    }
259
260 2
    /**
261
     * @param array $dsl
262
     * @param array $context
263
     * @return true
264
     * @throws \Exception
265
     */
266
    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...
267
    {
268
        if (!isset($dsl['file'])) {
269 1
            throw new \Exception("Can not delete file: name missing");
270
        }
271
272 1
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
273
        if (!file_exists($fileName)) {
274 1
            throw new \Exception("Can not move delete '$fileName': file missing");
275
        }
276 1
277
        $this->setReferences($fileName, $dsl);
278 1
279 1
        if (!unlink($fileName)) {
280 1
            throw new \Exception("Can not delete file '$fileName': operation failed");
281 1
        }
282
283
        return true;
284
    }
285 1
286
    protected function setReferences($fileName, $dsl)
287
    {
288
        if (!array_key_exists('references', $dsl)) {
289
            return false;
290
        }
291
292
        clearstatcache(true, $fileName);
293
        $stats = stat($fileName);
294
295
        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...
296
            throw new \Exception("Can not set references for file '$fileName': stat failed");
297
        }
298
299
        foreach ($dsl['references'] as $reference) {
300
            switch ($reference['attribute']) {
301
                case 'body':
302
                    $value = file_get_contents($fileName);
303
                    break;
304
                case 'size':
305
                    $value = $stats[7];
306
                    break;
307
                case 'uid':
308
                    $value = $stats[4];
309
                    break;
310
                case 'gid':
311
                    $value = $stats[5];
312
                    break;
313
                case 'atime':
314
                    $value = $stats[8];
315
                    break;
316
                case 'mtime':
317
                    $value = $stats[9];
318
                    break;
319
                case 'ctime':
320
                    $value = $stats[10];
321
                    break;
322
                default:
323
                    throw new \InvalidArgumentException('File executor does not support setting references for attribute ' . $reference['attribute']);
324
            }
325
326
            $overwrite = false;
327
            if (isset($reference['overwrite'])) {
328
                $overwrite = $reference['overwrite'];
329
            }
330
            $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...
331
        }
332
333
        return true;
334
    }
335
336
    /**
337
     * Replaces any references inside a string
338
     *
339
     * @param string $text
340
     * @return string
341
     */
342 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...
343
    {
344
        // 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
345
        $regexp = substr($this->referenceResolver->getRegexp(), 1, -1);
346
        // NB: here we assume that all regexp resolvers give us a regexp with a very specific format...
347
        $regexp = '/\[' . preg_replace(array('/^\^/'), array('', ''), $regexp) . '[^]]+\]/';
348
349
        $count = preg_match_all($regexp, $text, $matches);
350
        // $matches[0][] will have the matched full string eg.: [reference:example_reference]
351
        if ($count) {
352
            foreach ($matches[0] as $referenceIdentifier) {
353
                $reference = $this->referenceResolver->getReferenceValue(substr($referenceIdentifier, 1, -1));
354
                $text = str_replace($referenceIdentifier, $reference, $text);
355
            }
356
        }
357
358
        return $text;
359
    }
360
}
361