Completed
Push — master ( a20e35...2a3e96 )
by Greg
03:21
created

src/Task/Filesystem/FlattenDir.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Robo\Task\Filesystem;
4
5
use Robo\Result;
6
use Robo\Exception\TaskException;
7
use Symfony\Component\Finder\Finder;
8
9
/**
10
 * Searches for files in a nested directory structure and copies them to
11
 * a target directory with or without the parent directories. The task was
12
 * inspired by [gulp-flatten](https://www.npmjs.com/package/gulp-flatten).
13
 *
14
 * Example directory structure:
15
 *
16
 * ```
17
 * └── assets
18
 *     ├── asset-library1
19
 *     │   ├── README.md
20
 *     │   └── asset-library1.min.js
21
 *     └── asset-library2
22
 *         ├── README.md
23
 *         └── asset-library2.min.js
24
 * ```
25
 *
26
 * The following code will search the `*.min.js` files and copy them
27
 * inside a new `dist` folder:
28
 *
29
 * ``` php
30
 * <?php
31
 * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
32
 * // or use shortcut
33
 * $this->_flattenDir('assets/*.min.js', 'dist');
34
 * ?>
35
 * ```
36
 *
37
 * You can also define the target directory with an additional method, instead of
38
 * key/value pairs. More similar to the gulp-flatten syntax:
39
 *
40
 * ``` php
41
 * <?php
42
 * $this->taskFlattenDir(['assets/*.min.js'])
43
 *   ->to('dist')
44
 *   ->run();
45
 * ?>
46
 * ```
47
 *
48
 * You can also append parts of the parent directories to the target path. If you give
49
 * the value `1` to the `includeParents()` method, then the top parent will be appended
50
 * to the target directory resulting in a path such as `dist/assets/asset-library1.min.js`.
51
 *
52
 * If you give a negative number, such as `-1` (the same as specifying `array(0, 1)` then
53
 * the bottom parent will be appended, resulting in a path such as
54
 * `dist/asset-library1/asset-library1.min.js`.
55
 *
56
 * The top parent directory will always be starting from the relative path to the current
57
 * directory. You can override that with the `parentDir()` method. If in the above example
58
 * you would specify `assets`, then the top parent directory would be `asset-library1`.
59
 *
60
 * ``` php
61
 * <?php
62
 * $this->taskFlattenDir(['assets/*.min.js' => 'dist'])
63
 *   ->parentDir('assets')
64
 *   ->includeParents(1)
65
 *   ->run();
66
 * ?>
67
 * ```
68
 */
69
class FlattenDir extends BaseDir
70
{
71
    /**
72
     * @var int
73
     */
74
    protected $chmod = 0755;
75
76
    /**
77
     * @var int[]
78
     */
79
    protected $parents = array(0, 0);
80
81
    /**
82
     * @var string
83
     */
84
    protected $parentDir = '';
85
86
    /**
87
     * @var string
88
     */
89
    protected $to;
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function __construct($dirs)
95
    {
96
        parent::__construct($dirs);
97
        $this->parentDir = getcwd();
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function run()
104
    {
105
        // find the files
106
        $files = $this->findFiles($this->dirs);
107
108
        // copy the files
109
        $this->copyFiles($files);
0 ignored issues
show
It seems like $files defined by $this->findFiles($this->dirs) on line 106 can also be of type object<Robo\Result>; however, Robo\Task\Filesystem\FlattenDir::copyFiles() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
110
111
        $fileNoun = count($files) == 1 ? ' file' : ' files';
112
        $this->printTaskSuccess("Copied {count} $fileNoun to {destination}", ['count' => count($files), 'destination' => $this->to]);
113
114
        return Result::success($this);
115
    }
116
117
    /**
118
     * Sets the default folder permissions for the destination if it does not exist.
119
     *
120
     * @link http://en.wikipedia.org/wiki/Chmod
121
     * @link http://php.net/manual/en/function.mkdir.php
122
     * @link http://php.net/manual/en/function.chmod.php
123
     *
124
     * @param int $permission
125
     *
126
     * @return $this
127
     */
128
    public function dirPermissions($permission)
129
    {
130
        $this->chmod = (int) $permission;
131
132
        return $this;
133
    }
134
135
    /**
136
     * Sets the value from which direction and how much parent dirs should be included.
137
     * Accepts a positive or negative integer or an array with two integer values.
138
     *
139
     * @param int|int[] $parents
140
     *
141
     * @return $this
142
     *
143
     * @throws TaskException
144
     */
145
    public function includeParents($parents)
146
    {
147
        if (is_int($parents)) {
148
            // if an integer is given check whether it is for top or bottom parent
149
            if ($parents >= 0) {
150
                $this->parents[0] = $parents;
151
                return $this;
152
            }
153
            $this->parents[1] = 0 - $parents;
154
            return $this;
155
        }
156
157
        if (is_array($parents)) {
158
            // check if the array has two values no more, no less
159
            if (count($parents) == 2) {
160
                $this->parents = $parents;
161
                return $this;
162
            }
163
        }
164
165
        throw new TaskException($this, 'includeParents expects an integer or an array with two values');
166
    }
167
168
    /**
169
     * Sets the parent directory from which the relative parent directories will be calculated.
170
     *
171
     * @param string $dir
172
     *
173
     * @return $this
174
     */
175
    public function parentDir($dir)
176
    {
177
        if (!$this->fs->isAbsolutePath($dir)) {
178
            // attach the relative path to current working directory
179
            $dir = getcwd().'/'.$dir;
180
        }
181
        $this->parentDir = $dir;
182
183
        return $this;
184
    }
185
186
    /**
187
     * Sets the target directory where the files will be copied to.
188
     *
189
     * @param string $target
190
     *
191
     * @return $this
192
     */
193
    public function to($target)
194
    {
195
        $this->to = rtrim($target, '/');
196
197
        return $this;
198
    }
199
200
    /**
201
     * @param array $dirs
202
     *
203
     * @return array|\Robo\Result
204
     *
205
     * @throws \Robo\Exception\TaskException
206
     */
207 View Code Duplication
    protected function findFiles($dirs)
208
    {
209
        $files = array();
210
211
        // find the files
212
        foreach ($dirs as $k => $v) {
213
            // reset finder
214
            $finder = new Finder();
215
216
            $dir = $k;
217
            $to = $v;
218
            // check if target was given with the to() method instead of key/value pairs
219
            if (is_int($k)) {
220
                $dir = $v;
221
                if (isset($this->to)) {
222
                    $to = $this->to;
223
                } else {
224
                    throw new TaskException($this, 'target directory is not defined');
225
                }
226
            }
227
228
            try {
229
                $finder->files()->in($dir);
230
            } catch (\InvalidArgumentException $e) {
231
                // if finder cannot handle it, try with in()->name()
232
                if (strpos($dir, '/') === false) {
233
                    $dir = './'.$dir;
234
                }
235
                $parts = explode('/', $dir);
236
                $new_dir = implode('/', array_slice($parts, 0, -1));
237
                try {
238
                    $finder->files()->in($new_dir)->name(array_pop($parts));
239
                } catch (\InvalidArgumentException $e) {
240
                    return Result::fromException($this, $e);
241
                }
242
            }
243
244
            foreach ($finder as $file) {
245
                // store the absolute path as key and target as value in the files array
246
                $files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
247
            }
248
            $fileNoun = count($files) == 1 ? ' file' : ' files';
249
            $this->printTaskInfo("Found {count} $fileNoun in {dir}", ['count' => count($files), 'dir' => $dir]);
250
        }
251
252
        return $files;
253
    }
254
255
    /**
256
     * @param string $file
257
     * @param string $to
258
     *
259
     * @return string
260
     */
261
    protected function getTarget($file, $to)
262
    {
263
        $target = $to.'/'.basename($file);
264
        if ($this->parents !== array(0, 0)) {
265
            // if the parent is set, create additional directories inside target
266
            // get relative path to parentDir
267
            $rel_path = $this->fs->makePathRelative(dirname($file), $this->parentDir);
268
            // get top parents and bottom parents
269
            $parts = explode('/', rtrim($rel_path, '/'));
270
            $prefix_dir = '';
271
            $prefix_dir .= ($this->parents[0] > 0 ? implode('/', array_slice($parts, 0, $this->parents[0])).'/' : '');
272
            $prefix_dir .= ($this->parents[1] > 0 ? implode('/', array_slice($parts, (0 - $this->parents[1]), $this->parents[1])) : '');
273
            $prefix_dir = rtrim($prefix_dir, '/');
274
            $target = $to.'/'.$prefix_dir.'/'.basename($file);
275
        }
276
277
        return $target;
278
    }
279
280
    /**
281
     * @param array $files
282
     */
283
    protected function copyFiles($files)
284
    {
285
        // copy the files
286
        foreach ($files as $from => $to) {
287
            // check if target dir exists
288
            if (!is_dir(dirname($to))) {
289
                $this->fs->mkdir(dirname($to), $this->chmod);
290
            }
291
            $this->fs->copy($from, $to);
292
        }
293
    }
294
}
295