FileUtils::copyFile()   D
last analyzed

Complexity

Conditions 19
Paths 121

Size

Total Lines 65
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 19.0206

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
c 1
b 0
f 0
dl 0
loc 65
ccs 25
cts 26
cp 0.9615
rs 4.3416
cc 19
nc 121
nop 9
crap 19.0206

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Io;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Filter\ChainReaderHelper;
26
use Phing\Phing;
27
use Phing\Project;
28
use Phing\Util\Character;
29
use Phing\Util\StringHelper;
30
31
/**
32
 * File utility class.
33
 * - handles os independent stuff etc
34
 * - mapper stuff
35
 * - filter stuff.
36
 */
37
class FileUtils
38
{
39
    /**
40
     * path separator string, static, obtained from FileSystem (; or :).
41
     */
42
    private static $pathSeparator;
43
44
    /**
45
     * separator string, static, obtained from FileSystem.
46
     */
47
    private static $separator;
48
49
    /**
50
     * @var false
51
     */
52
    private $dosWithDrive;
53
54
    /**
55
     * @throws IOException
56
     */
57 6
    public static function getPathSeparator(): string
58
    {
59 6
        if (null === self::$pathSeparator) {
60
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
61
        }
62
63 6
        return self::$pathSeparator;
64
    }
65
66
    /**
67
     * @throws IOException
68
     */
69 941
    public static function getSeparator(): string
70
    {
71 941
        if (null === self::$separator) {
72
            self::$separator = FileSystem::getFileSystem()->getSeparator();
73
        }
74
75 941
        return self::$separator;
76
    }
77
78
    /**
79
     * Returns the path to the temp directory.
80
     *
81
     * @return string
82
     */
83 9
    public static function getTempDir()
84
    {
85 9
        return Phing::getProperty('php.tmpdir');
86
    }
87
88
    /**
89
     * Returns the default file/dir creation mask value
90
     * (The mask value is prepared w.r.t the current user's file-creation mask value).
91
     *
92
     * @param bool $dirmode Directory creation mask to select
93
     *
94
     * @return int Creation Mask in octal representation
95
     */
96
    public static function getDefaultFileCreationMask($dirmode = false): int
97
    {
98
        // Preparing the creation mask base permission
99
        $permission = (true === $dirmode) ? 0777 : 0666;
100
101
        // Default mask information
102
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
103
104
        return octdec($defaultmask);
0 ignored issues
show
Bug Best Practice introduced by
The expression return octdec($defaultmask) could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
105
    }
106
107
    /**
108
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
109
     * simply returns passed reader.
110
     *
111
     * @param Reader $in            reader to modify (if appropriate)
112
     * @param array  &$filterChains filter chains to apply
113
     *
114
     * @return Reader assembled Reader (w/ filter chains)
115
     */
116 42
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
117
    {
118 42
        if (!empty($filterChains)) {
119 32
            $crh = new ChainReaderHelper();
120 32
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
121 32
            $crh->setPrimaryReader($in);
122 32
            $crh->setFilterChains($filterChains);
123 32
            $crh->setProject($project);
124
125 32
            return $crh->getAssembledReader();
126
        }
127
128 11
        return $in;
129
    }
130
131
    /**
132
     * Copies a file using filter chains.
133
     *
134
     * @param bool  $overwrite
135
     * @param bool  $preserveLastModified
136
     * @param array $filterChains
137
     * @param int   $mode
138
     * @param bool  $preservePermissions
139
     *
140
     * @throws IOException
141
     */
142 53
    public function copyFile(
143
        File $sourceFile,
144
        File $destFile,
145
        Project $project,
146
        $overwrite = false,
147
        $preserveLastModified = true,
148
        &$filterChains = null,
149
        $mode = 0755,
150
        $preservePermissions = true,
151
        int $granularity = 0
152
    ) {
153
        if (
154 53
            $overwrite
155 42
            || !$destFile->exists()
156 53
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
157
        ) {
158 52
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
159 1
                $destFile->delete();
160
            }
161
162
            // ensure that parent dir of dest file exists!
163 52
            $parent = $destFile->getParentFile();
164 52
            if (null !== $parent && !$parent->exists()) {
165
                // Setting source directory permissions to target
166
                // (On permissions preservation, the target directory permissions
167
                // will be inherited from the source directory, otherwise the 'mode'
168
                // will be used)
169 7
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
170
171 7
                $parent->mkdirs($dirMode);
172
            }
173
174 52
            if ((is_array($filterChains)) && (!empty($filterChains))) {
175 23
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
176 23
                $out = new BufferedWriter(new FileWriter($destFile));
177
178
                // New read() methods returns a big buffer.
179 23
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
180 23
                    $out->write($buffer);
181
                }
182
183 23
                if (null !== $in) {
184 23
                    $in->close();
185
                }
186 23
                if (null !== $out) {
187 23
                    $out->close();
188
                }
189
190
                // Set/Copy the permissions on the target
191 23
                if (true === $preservePermissions) {
192 23
                    $destFile->setMode($sourceFile->getMode());
193
                }
194
            } else {
195
                // simple copy (no filtering)
196 29
                $sourceFile->copyTo($destFile);
197
198
                // By default, PHP::Copy also copies the file permissions. Therefore,
199
                // re-setting the mode with the "user file-creation mask" information.
200 29
                if (false === $preservePermissions) {
201
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask());
202
                }
203
            }
204
205 52
            if ($preserveLastModified && !$destFile->isLink()) {
206 2
                $destFile->setLastModified($sourceFile->lastModified());
207
            }
208
        }
209
    }
210
211
    /**
212
     * Attempts to rename a file from a source to a destination.
213
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
214
     * Otherwise, the source file is renamed only if the destination file is older than it.
215
     *
216
     * @param mixed $overwrite
217
     *
218
     * @throws IOException
219
     */
220 1
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false): void
221
    {
222
        // ensure that parent dir of dest file exists!
223 1
        $parent = $destFile->getParentFile();
224 1
        if (null !== $parent) {
225 1
            if (!$parent->exists()) {
226
                $parent->mkdirs();
227
            }
228
        }
229
230 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
231 1
            if ($destFile->exists()) {
232
                try {
233
                    $destFile->delete();
234
                } catch (Exception $e) {
235
                    throw new BuildException(
236
                        'Unable to remove existing file ' . $destFile->__toString() . ': ' . $e->getMessage()
237
                    );
238
                }
239
            }
240
        }
241
242 1
        $sourceFile->renameTo($destFile);
243
    }
244
245
    /**
246
     * Interpret the filename as a file relative to the given file -
247
     * unless the filename already represents an absolute filename.
248
     *
249
     * @param File   $file     the "reference" file for relative paths. This
250
     *                         instance must be an absolute file and must
251
     *                         not contain ./ or ../ sequences (same for \
252
     *                         instead of /).
253
     * @param string $filename a file name
254
     *
255
     * @throws IOException
256
     *
257
     * @return File A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
258
     *              and uses the correct separator for the current platform.
259
     */
260 502
    public function resolveFile(File $file, string $filename): File
261
    {
262
        // remove this and use the static class constant File::separator
263
        // as soon as ZE2 is ready
264 502
        $fs = FileSystem::getFileSystem();
265
266 502
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
267
268
        // deal with absolute files
269
        if (
270 502
            StringHelper::startsWith($fs->getSeparator(), $filename)
271 502
            || (strlen($filename) >= 2
272 502
                && Character::isLetter($filename[0])
273 502
                && ':' === $filename[1])
274
        ) {
275 111
            return new File($this->normalize($filename));
276
        }
277
278 477
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && ':' === $filename[1]) {
279
            return new File($this->normalize($filename));
280
        }
281
282 477
        $helpFile = new File($file->getAbsolutePath());
283
284 477
        $tok = strtok($filename, $fs->getSeparator());
285 477
        while (false !== $tok) {
286 477
            $part = $tok;
287 477
            if ('..' === $part) {
288 174
                $parentFile = $helpFile->getParent();
289 174
                if (null === $parentFile) {
290
                    $msg = "The file or path you specified ({$filename}) is invalid relative to " . $file->getPath();
291
292
                    throw new IOException($msg);
293
                }
294 174
                $helpFile = new File($parentFile);
295 477
            } elseif ('.' !== $part) {
296 446
                $helpFile = new File($helpFile, $part);
297
            }
298 477
            $tok = strtok($fs->getSeparator());
299
        }
300
301 477
        return new File($helpFile->getAbsolutePath());
302
    }
303
304
    /**
305
     * Normalize the given absolute path.
306
     *
307
     * This includes:
308
     *   - Uppercase the drive letter if there is one.
309
     *   - Remove redundant slashes after the drive spec.
310
     *   - resolve all ./, .\, ../ and ..\ sequences.
311
     *   - DOS style paths that start with a drive letter will have
312
     *     \ as the separator.
313
     *
314
     * @param string $path path to normalize
315
     *
316
     * @throws IOException
317
     * @throws BuildException
318
     */
319 924
    public function normalize(string $path): string
320
    {
321 924
        $dissect = $this->dissect($path);
322 924
        $sep = self::getSeparator();
323
324 924
        $s = [];
325 924
        $s[] = $dissect[0];
326 924
        $tok = strtok($dissect[1], $sep);
327 924
        while (false !== $tok) {
328 920
            $thisToken = $tok;
329 920
            if ('.' === $thisToken) {
330
                $tok = strtok($sep);
331
332
                continue;
333
            }
334
335 920
            if ('..' === $thisToken) {
336
                if (count($s) < 2) {
337
                    // using '..' in path that is too short
338
                    throw new IOException("Cannot resolve path: {$path}");
339
                }
340
341
                array_pop($s);
342
            } else { // plain component
343 920
                $s[] = $thisToken;
344
            }
345 920
            $tok = strtok($sep);
346
        }
347
348 924
        $sb = '';
349 924
        foreach ($s as $i => $v) {
350 924
            if ($i > 1) {
351
                // not before the filesystem root and not after it, since root
352
                // already contains one
353 920
                $sb .= $sep;
354
            }
355 924
            $sb .= $v;
356
        }
357
358 924
        $path = $sb;
359 924
        if (true === $this->dosWithDrive) {
360
            $path = str_replace('/', '\\', $path);
361
        }
362
363 924
        return $path;
364
    }
365
366
    /**
367
     * Dissect the specified absolute path.
368
     *
369
     * @throws BuildException
370
     * @throws IOException
371
     *
372
     * @return array {root, remainig path}
373
     */
374 924
    public function dissect(string $path): array
375
    {
376 924
        $sep = self::getSeparator();
377 924
        $path = str_replace(['\\', '/'], $sep, $path);
378
379
        // make sure we are dealing with an absolute path
380
        if (
381 924
            !StringHelper::startsWith($sep, $path)
382 924
            && !(strlen($path) >= 2
383 924
                && Character::isLetter($path[0])
384 924
                && ':' === $path[1])
385
        ) {
386
            throw new BuildException("{$path} is not an absolute path");
387
        }
388
389 924
        $this->dosWithDrive = false;
390 924
        $root = null;
391
392
        // Eliminate consecutive slashes after the drive spec
393
394 924
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && ':' === $path[1]) {
395
            $this->dosWithDrive = true;
0 ignored issues
show
Documentation Bug introduced by
The property $dosWithDrive was declared of type false, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
396
397
            $ca = str_replace('/', '\\', $path);
398
399
            $path = strtoupper($ca[0]) . ':';
400
401
            for ($i = 2, $_i = strlen($ca); $i < $_i; ++$i) {
402
                if (
403
                    ('\\' !== $ca[$i])
404
                    || ('\\' === $ca[$i]
405
                        && '\\' !== $ca[$i - 1])
406
                ) {
407
                    $path .= $ca[$i];
408
                }
409
            }
410
411
            $path = str_replace('\\', $sep, $path);
412
413
            if (2 === strlen($path)) {
414
                $root = $path;
415
                $path = '';
416
            } else {
417
                $root = substr($path, 0, 3);
418
                $path = substr($path, 3);
419
            }
420
        } else {
421 924
            if (1 === strlen($path)) {
422 4
                $root = $sep;
423 4
                $path = '';
424
            } else {
425 920
                if ($path[1] === $sep) {
426
                    // UNC drive
427
                    $root = $sep . $sep;
428
                    $path = substr($path, 2);
429
                } else {
430 920
                    $root = $sep;
431 920
                    $path = substr($path, 1);
432
                }
433
            }
434
        }
435
436 924
        return [$root, $path];
437
    }
438
439
    /**
440
     * Create a temporary file in a given directory.
441
     *
442
     * <p>The file denoted by the returned abstract pathname did not
443
     * exist before this method was invoked, any subsequent invocation
444
     * of this method will yield a different file name.</p>
445
     *
446
     * @param string $prefix       prefix before the random number
447
     * @param string $suffix       file extension; include the '.'.
448
     * @param File   $parentDir    directory to create the temporary file in;
449
     *                             sys_get_temp_dir() used if not specified
450
     * @param bool   $deleteOnExit whether to set the tempfile for deletion on
451
     *                             normal exit
452
     * @param bool   $createFile   true if the file must actually be created. If false
453
     *                             chances exist that a file with the same name is
454
     *                             created in the time between invoking this method
455
     *                             and the moment the file is actually created. If
456
     *                             possible set to true.
457
     *
458
     * @throws BuildException
459
     *
460
     * @return File a File reference to the new temporary file
461
     */
462 2
    public function createTempFile(
463
        $prefix,
464
        $suffix,
465
        ?File $parentDir = null,
466
        $deleteOnExit = false,
467
        $createFile = false
468
    ): File {
469 2
        $result = null;
470 2
        $parent = (null === $parentDir) ? self::getTempDir() : $parentDir->getPath();
471
472 2
        if ($createFile) {
473
            try {
474
                $directory = new File($parent);
475
                // quick but efficient hack to create a unique filename ;-)
476
                $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
477
                do {
478
                    $result = new File($directory, $prefix . substr(md5((string) microtime()), 0, 8) . $suffix);
479
                } while (file_exists($result->getPath()));
480
481
                $fs = FileSystem::getFileSystem();
482
                $fs->createNewFile($result->getPath());
483
                $fs->lock($result);
484
            } catch (IOException $e) {
485
                throw new BuildException('Could not create tempfile in ' . $parent, $e);
486
            }
487
        } else {
488
            do {
489 2
                $result = new File($parent, $prefix . substr(md5((string) microtime()), 0, 8) . $suffix);
490 2
            } while ($result->exists());
491
        }
492
493 2
        if ($deleteOnExit) {
494
            $result->deleteOnExit();
495
        }
496
497 2
        return $result;
498
    }
499
500
    /**
501
     * @throws IOException
502
     *
503
     * @return bool whether contents of two files is the same
504
     */
505 14
    public function contentEquals(File $file1, File $file2): bool
506
    {
507 14
        if (!($file1->exists() && $file2->exists())) {
508 2
            return false;
509
        }
510
511 13
        if (!($file1->canRead() && $file2->canRead())) {
512
            return false;
513
        }
514
515 13
        if ($file1->isDirectory() || $file2->isDirectory()) {
516 1
            return false;
517
        }
518
519 13
        $c1 = file_get_contents($file1->getAbsolutePath());
520 13
        $c2 = file_get_contents($file2->getAbsolutePath());
521
522 13
        return trim($c1) === trim($c2);
523
    }
524
}
525