FileUtils::copyFile()   D
last analyzed

Complexity

Conditions 19
Paths 121

Size

Total Lines 65
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
dl 0
loc 65
rs 4.3416
c 1
b 0
f 0
cc 19
nc 121
nop 9

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
    public static function getPathSeparator(): string
58
    {
59
        if (null === self::$pathSeparator) {
60
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
61
        }
62
63
        return self::$pathSeparator;
64
    }
65
66
    /**
67
     * @throws IOException
68
     */
69
    public static function getSeparator(): string
70
    {
71
        if (null === self::$separator) {
72
            self::$separator = FileSystem::getFileSystem()->getSeparator();
73
        }
74
75
        return self::$separator;
76
    }
77
78
    /**
79
     * Returns the path to the temp directory.
80
     *
81
     * @return string
82
     */
83
    public static function getTempDir()
84
    {
85
        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
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
117
    {
118
        if (!empty($filterChains)) {
119
            $crh = new ChainReaderHelper();
120
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
121
            $crh->setPrimaryReader($in);
122
            $crh->setFilterChains($filterChains);
123
            $crh->setProject($project);
124
125
            return $crh->getAssembledReader();
126
        }
127
128
        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
    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
            $overwrite
155
            || !$destFile->exists()
156
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
157
        ) {
158
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
159
                $destFile->delete();
160
            }
161
162
            // ensure that parent dir of dest file exists!
163
            $parent = $destFile->getParentFile();
164
            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
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
170
171
                $parent->mkdirs($dirMode);
172
            }
173
174
            if ((is_array($filterChains)) && (!empty($filterChains))) {
175
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
176
                $out = new BufferedWriter(new FileWriter($destFile));
177
178
                // New read() methods returns a big buffer.
179
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
180
                    $out->write($buffer);
181
                }
182
183
                if (null !== $in) {
184
                    $in->close();
185
                }
186
                if (null !== $out) {
187
                    $out->close();
188
                }
189
190
                // Set/Copy the permissions on the target
191
                if (true === $preservePermissions) {
192
                    $destFile->setMode($sourceFile->getMode());
193
                }
194
            } else {
195
                // simple copy (no filtering)
196
                $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
                if (false === $preservePermissions) {
201
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask());
202
                }
203
            }
204
205
            if ($preserveLastModified && !$destFile->isLink()) {
206
                $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
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false): void
221
    {
222
        // ensure that parent dir of dest file exists!
223
        $parent = $destFile->getParentFile();
224
        if (null !== $parent) {
225
            if (!$parent->exists()) {
226
                $parent->mkdirs();
227
            }
228
        }
229
230
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
231
            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
        $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
    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
        $fs = FileSystem::getFileSystem();
265
266
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
267
268
        // deal with absolute files
269
        if (
270
            StringHelper::startsWith($fs->getSeparator(), $filename)
271
            || (strlen($filename) >= 2
272
                && Character::isLetter($filename[0])
273
                && ':' === $filename[1])
274
        ) {
275
            return new File($this->normalize($filename));
276
        }
277
278
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && ':' === $filename[1]) {
279
            return new File($this->normalize($filename));
280
        }
281
282
        $helpFile = new File($file->getAbsolutePath());
283
284
        $tok = strtok($filename, $fs->getSeparator());
285
        while (false !== $tok) {
286
            $part = $tok;
287
            if ('..' === $part) {
288
                $parentFile = $helpFile->getParent();
289
                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
                $helpFile = new File($parentFile);
295
            } elseif ('.' !== $part) {
296
                $helpFile = new File($helpFile, $part);
297
            }
298
            $tok = strtok($fs->getSeparator());
299
        }
300
301
        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
    public function normalize(string $path): string
320
    {
321
        $dissect = $this->dissect($path);
322
        $sep = self::getSeparator();
323
324
        $s = [];
325
        $s[] = $dissect[0];
326
        $tok = strtok($dissect[1], $sep);
327
        while (false !== $tok) {
328
            $thisToken = $tok;
329
            if ('.' === $thisToken) {
330
                $tok = strtok($sep);
331
332
                continue;
333
            }
334
335
            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
                $s[] = $thisToken;
344
            }
345
            $tok = strtok($sep);
346
        }
347
348
        $sb = '';
349
        foreach ($s as $i => $v) {
350
            if ($i > 1) {
351
                // not before the filesystem root and not after it, since root
352
                // already contains one
353
                $sb .= $sep;
354
            }
355
            $sb .= $v;
356
        }
357
358
        $path = $sb;
359
        if (true === $this->dosWithDrive) {
360
            $path = str_replace('/', '\\', $path);
361
        }
362
363
        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
    public function dissect(string $path): array
375
    {
376
        $sep = self::getSeparator();
377
        $path = str_replace(['\\', '/'], $sep, $path);
378
379
        // make sure we are dealing with an absolute path
380
        if (
381
            !StringHelper::startsWith($sep, $path)
382
            && !(strlen($path) >= 2
383
                && Character::isLetter($path[0])
384
                && ':' === $path[1])
385
        ) {
386
            throw new BuildException("{$path} is not an absolute path");
387
        }
388
389
        $this->dosWithDrive = false;
390
        $root = null;
391
392
        // Eliminate consecutive slashes after the drive spec
393
394
        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
            if (1 === strlen($path)) {
422
                $root = $sep;
423
                $path = '';
424
            } else {
425
                if ($path[1] === $sep) {
426
                    // UNC drive
427
                    $root = $sep . $sep;
428
                    $path = substr($path, 2);
429
                } else {
430
                    $root = $sep;
431
                    $path = substr($path, 1);
432
                }
433
            }
434
        }
435
436
        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
    public function createTempFile(
463
        $prefix,
464
        $suffix,
465
        ?File $parentDir = null,
466
        $deleteOnExit = false,
467
        $createFile = false
468
    ): File {
469
        $result = null;
470
        $parent = (null === $parentDir) ? self::getTempDir() : $parentDir->getPath();
471
472
        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(time()), 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
                $result = new File($parent, $prefix . substr(md5((string) time()), 0, 8) . $suffix);
490
            } while ($result->exists());
491
        }
492
493
        if ($deleteOnExit) {
494
            $result->deleteOnExit();
495
        }
496
497
        return $result;
498
    }
499
500
    /**
501
     * @throws IOException
502
     *
503
     * @return bool whether contents of two files is the same
504
     */
505
    public function contentEquals(File $file1, File $file2): bool
506
    {
507
        if (!($file1->exists() && $file2->exists())) {
508
            return false;
509
        }
510
511
        if (!($file1->canRead() && $file2->canRead())) {
512
            return false;
513
        }
514
515
        if ($file1->isDirectory() || $file2->isDirectory()) {
516
            return false;
517
        }
518
519
        $c1 = file_get_contents($file1->getAbsolutePath());
520
        $c2 = file_get_contents($file2->getAbsolutePath());
521
522
        return trim($c1) === trim($c2);
523
    }
524
}
525