Completed
Push — master ( 2e9676...cd35b7 )
by Gaetano
35:20
created

FileExecutor::execute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
dl 16
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
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
    protected $supportedStepTypes = array('file');
11
    protected $supportedActions = array('load', 'save', 'copy', 'move', 'delete', 'append');
12
13
    /** @var PrefixBasedResolverInterface $referenceResolver */
14
    protected $referenceResolver;
15
16
    public function __construct(PrefixBasedResolverInterface $referenceResolver)
17
    {
18
        $this->referenceResolver = $referenceResolver;
19
    }
20
21
    /**
22
     * @param MigrationStep $step
23
     * @return mixed
24
     * @throws \Exception
25
     */
26 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...
27
    {
28
        parent::execute($step);
29
30
        if (!isset($step->dsl['mode'])) {
31
            throw new \Exception("Invalid step definition: missing 'mode'");
32
        }
33
34
        $action = $step->dsl['mode'];
35
36
        if (!in_array($action, $this->supportedActions)) {
37
            throw new \Exception("Invalid step definition: value '$action' is not allowed for 'mode'");
38
        }
39
40
        return $this->$action($step->dsl, $step->context);
41
    }
42
43
    /**
44
     * @param array $dsl
45
     * @param array $context
46
     * @return string
47
     * @throws \Exception
48
     */
49
    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...
50
    {
51
        if (!isset($dsl['file'])) {
52
            throw new \Exception("Can not load file: name missing");
53
        }
54
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
55
        if (!file_exists($fileName)) {
56
            throw new \Exception("Can not move load '$fileName': file missing");
57
        }
58
59
        $this->setReferences($fileName, $dsl);
60
61
        return file_get_contents($fileName);
62
    }
63
64
    /**
65
     * @param array $dsl
66
     * @param array $context
67
     * @return int
68
     * @throws \Exception
69
     */
70
    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...
71
    {
72
        if (!isset($dsl['file']) || !isset($dsl['body'])) {
73
            throw new \Exception("Can not save file: name or body missing");
74
        }
75
76 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...
77
            $contents = $this->resolveReferencesInText($dsl['body']);
78
        } else {
79
            throw new \Exception("Can not save file: body tag must be a string");
80
        }
81
82
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
83
84
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
85
        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...
86
            throw new \Exception("Can not save file '$fileName: file already exists");
87
        }
88
89
        $return = file_put_contents($fileName, $contents);
90
91
        $this->setReferences($fileName, $dsl);
92
93
        return $return;
94
    }
95
96
    /**
97
     * @param array $dsl
98
     * @param array $context
99
     * @return int
100
     * @throws \Exception
101
     */
102
    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...
103
    {
104
        if (!isset($dsl['file']) || !isset($dsl['body'])) {
105
            throw new \Exception("Can not append to file: name or body missing");
106
        }
107
108 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...
109
            $contents = $this->resolveReferencesInText($dsl['body']);
110
        } else {
111
            throw new \Exception("Can not append to file: body tag must be a string");
112
        }
113
114
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
115
116
        $return = file_put_contents($fileName, $contents, FILE_APPEND);
117
118
        $this->setReferences($fileName, $dsl);
119
120
        return $return;
121
    }
122
123
    /**
124
     * @param array $dsl
125
     * @param array $context
126
     * @return true
127
     * @throws \Exception
128
     */
129 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...
130
    {
131
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
132
            throw new \Exception("Can not copy file: from or to missing");
133
        }
134
135
        $fileName = $this->referenceResolver->resolveReference($dsl['from']);
136
        if (!file_exists($fileName)) {
137
            throw new \Exception("Can not copy file '$fileName': file missing");
138
        }
139
140
        $this->setReferences($fileName, $dsl);
141
142
        $to = $this->referenceResolver->resolveReference($dsl['to']);
143
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
144
        if (!$overwrite && file_exists($to)) {
145
            throw new \Exception("Can not copy file to '$to: file already exists");
146
        }
147
148
        if (!copy($fileName, $to)) {
149
            throw new \Exception("Can not copy file '$fileName' to '$to': operation failed");
150
        }
151
152
        return true;
153
    }
154
155
    /**
156
     * @param array $dsl
157
     * @param array $context
158
     * @return true
159
     * @throws \Exception
160
     */
161 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...
162
    {
163
        if (!isset($dsl['from']) || !isset($dsl['to'])) {
164
            throw new \Exception("Can not move file: from or to missing");
165
        }
166
167
        $fileName = $this->referenceResolver->resolveReference($dsl['from']);
168
        if (!file_exists($fileName)) {
169
            throw new \Exception("Can not move file '$fileName': file missing");
170
        }
171
172
        $this->setReferences($fileName, $dsl);
173
174
        $to = $this->referenceResolver->resolveReference($dsl['to']);
175
        $overwrite = isset($dsl['overwrite']) ? $overwrite = $dsl['overwrite'] : false;
176
        if (!$overwrite && file_exists($to)) {
177
            throw new \Exception("Can not move to '$to': file already exists");
178
        }
179
180
        if (!rename($fileName, $to)) {
181
            throw new \Exception("Can not move file '$fileName': operation failed");
182
        }
183
184
        return true;
185
    }
186
187
    /**
188
     * @param array $dsl
189
     * @param array $context
190
     * @return true
191
     * @throws \Exception
192
     */
193
    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...
194
    {
195
        if (!isset($dsl['file'])) {
196
            throw new \Exception("Can not delete file: name missing");
197
        }
198
199
        $fileName = $this->referenceResolver->resolveReference($dsl['file']);
200
        if (!file_exists($fileName)) {
201
            throw new \Exception("Can not move delete '$fileName': file missing");
202
        }
203
204
        $this->setReferences($fileName, $dsl);
205
206
        if (!unlink($fileName)) {
207
            throw new \Exception("Can not delete file '$fileName': operation failed");
208
        }
209
210
        return true;
211
    }
212
213
    protected function setReferences($fileName, $dsl)
214
    {
215
        if (!array_key_exists('references', $dsl)) {
216
            return false;
217
        }
218
219
        $stats = stat($fileName);
220
221
        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...
222
            throw new \Exception("Can not set references for file '$fileName': stat failed");
223
        }
224
225
        foreach ($dsl['references'] as $reference) {
226
            switch ($reference['attribute']) {
227
                case 'body':
228
                    $value = file_get_contents($fileName);
229
                    break;
230
                case 'size':
231
                    $value = $stats[7];
232
                    break;
233
                case 'uid':
234
                    $value = $stats[4];
235
                    break;
236
                case 'gid':
237
                    $value = $stats[5];
238
                    break;
239
                case 'atime':
240
                    $value = $stats[8];
241
                    break;
242
                case 'mtime':
243
                    $value = $stats[9];
244
                    break;
245
                case 'ctime':
246
                    $value = $stats[10];
247
                    break;
248
                default:
249
                    throw new \InvalidArgumentException('File executor does not support setting references for attribute ' . $reference['attribute']);
250
            }
251
252
            $overwrite = false;
253
            if (isset($reference['overwrite'])) {
254
                $overwrite = $reference['overwrite'];
255
            }
256
            $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...
257
        }
258
259
        return true;
260
    }
261
262
    /**
263
     * Replaces any references inside a string
264
     *
265
     * @param string
266
     * @return string
267
     */
268 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...
269
    {
270
        // 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
271
        $regexp = substr($this->referenceResolver->getRegexp(), 1, -1);
272
        // NB: here we assume that all regexp resolvers give us a regexp with a very specific format...
273
        $regexp = '/\[' . preg_replace(array('/^\^/'), array('', ''), $regexp) . '[^]]+\]/';
274
275
        $count = preg_match_all($regexp, $text, $matches);
276
        // $matches[0][] will have the matched full string eg.: [reference:example_reference]
277
        if ($count) {
278
            foreach ($matches[0] as $referenceIdentifier) {
279
                $reference = $this->referenceResolver->getReferenceValue(substr($referenceIdentifier, 1, -1));
280
                $text = str_replace($referenceIdentifier, $reference, $text);
281
            }
282
        }
283
284
        return $text;
285
    }
286
}
287