Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

CopyTask::copyToSingleDestination()   A

Complexity

Conditions 3
Paths 9

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 3.0813

Importance

Changes 0
Metric Value
cc 3
eloc 25
nc 9
nop 8
dl 0
loc 41
ccs 19
cts 24
cp 0.7917
crap 3.0813
rs 9.52
c 0
b 0
f 0

How to fix   Many Parameters   

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Tasks\System;
21
22
use Phing\Exception\BuildException;
23
use Phing\Io\FileUtils;
24
use Phing\Io\IOException;
25
use Phing\Io\File;
26
use Phing\Io\SourceFileScanner;
27
use Phing\Mapper\FileNameMapper;
28
use Phing\Mapper\FlattenMapper;
29
use Phing\Mapper\IdentityMapper;
30
use Phing\Project;
31
use Phing\Task;
32
use Phing\Type\Element\FilterChainAware;
33
use Phing\Type\Element\ResourceAware;
34
use Phing\Type\Mapper;
35
use Phing\Util\RegisterSlot;
36
37
/**
38
 * A phing copy task.  Copies a file or directory to a new file
39
 * or directory.  Files are only copied if the source file is newer
40
 * than the destination file, or when the destination file does not
41
 * exist. It is possible to explicitly overwrite existing files.
42
 *
43
 * @author Andreas Aderhold, [email protected]
44
 *
45
 * @package phing.tasks.system
46
 */
47
class CopyTask extends Task
48
{
49
    use ResourceAware;
50
    use FilterChainAware;
51
52
    /**
53
     * @var File
54
     */
55
    protected $file = null; // the source file (from xml attribute)
56
57
    /**
58
     * @var File
59
     */
60
    protected $destFile = null; // the destiantion file (from xml attribute)
61
62
    /**
63
     * @var File
64
     */
65
    protected $destDir = null; // the destination dir (from xml attribute)
66
67
    protected $overwrite = false; // overwrite destination (from xml attribute)
68
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
69
    protected $preservePermissions = true; // sync permissions (from xml attribute)
70
    protected $includeEmpty = true; // include empty dirs? (from XML)
71
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
72
73
    /**
74
     * @var Mapper
75
     */
76
    protected $mapperElement = null;
77
78
    protected $fileCopyMap = []; // asoc array containing mapped file names
79
    protected $dirCopyMap = []; // asoc array containing mapped file names
80
    protected $completeDirMap = []; // asoc array containing complete dir names
81
82
    /**
83
     * @var FileUtils
84
     */
85
    protected $fileUtils = null; // a instance of fileutils
86
87
    protected $verbosity = Project::MSG_VERBOSE;
88
89
    /**
90
     * @var int $mode
91
     */
92
    protected $mode = 0; // mode to create directories with
93
94
    /**
95
     * @var bool $haltonerror
96
     */
97
    protected $haltonerror = true; // stop build on errors
98
99
    protected $enableMultipleMappings = false;
100
101
    /** @var int $granularity */
102
    protected $granularity = 0;
103
104
    /**
105
     * Sets up this object internal stuff.
106
     * i.e. the Fileutils instance and default mode.
107
     */
108 60
    public function __construct()
109
    {
110 60
        parent::__construct();
111 60
        $this->fileUtils = new FileUtils();
112 60
        $this->mode = 0777 - umask();
113 60
    }
114
115
    /**
116
     * Set the number of seconds leeway to give before deciding a
117
     * target is out of date.
118
     *
119
     * @param int $granularity the granularity used to decide if a target is out of date.
120
     */
121 2
    public function setGranularity(int $granularity): void
122
    {
123 2
        $this->granularity = $granularity;
124 2
    }
125
126
    /**
127
     * Set the overwrite flag. IntrospectionHelper takes care of
128
     * booleans in set* methods so we can assume that the right
129
     * value (boolean primitive) is coming in here.
130
     *
131
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
132
     *
133
     * @return void
134
     */
135 11
    public function setOverwrite($bool)
136
    {
137 11
        $this->overwrite = (bool) $bool;
138 11
    }
139
140
    /**
141
     * Set whether files copied from directory trees will be "flattened"
142
     * into a single directory.  If there are multiple files with
143
     * the same name in the source directory tree, only the first
144
     * file will be copied into the "flattened" directory, unless
145
     * the forceoverwrite attribute is true.
146
     *
147
     * @param bool $flatten if true flatten the destination directory. Default
148
     *                is false.
149
     */
150
    public function setFlatten($flatten)
151
    {
152
        $this->flatten = $flatten;
153
    }
154
155
    /**
156
     * Used to force listing of all names of copied files.
157
     *
158
     * @param boolean $verbosity
159
     */
160 1
    public function setVerbose($verbosity)
161
    {
162 1
        if ($verbosity) {
163 1
            $this->verbosity = Project::MSG_INFO;
164
        } else {
165
            $this->verbosity = Project::MSG_VERBOSE;
166
        }
167 1
    }
168
169
    /**
170
     * @see CopyTask::setPreserveLastModified
171
     * @param $bool
172
     */
173
    public function setTstamp($bool)
174
    {
175
        $this->setPreserveLastModified($bool);
176
    }
177
178
    /**
179
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
180
     * booleans in set* methods so we can assume that the right
181
     * value (boolean primitive) is coming in here.
182
     *
183
     * @param  boolean $bool Preserve the timestamp on the destination file
184
     * @return void
185
     */
186 1
    public function setPreserveLastModified($bool)
187
    {
188 1
        $this->preserveLMT = (bool) $bool;
189 1
    }
190
191
    /**
192
     * Set the preserve permissions flag. IntrospectionHelper takes care of
193
     * booleans in set* methods so we can assume that the right
194
     * value (boolean primitive) is coming in here.
195
     *
196
     * @param  boolean $bool Preserve the timestamp on the destination file
197
     * @return void
198
     */
199
    public function setPreservepermissions($bool)
200
    {
201
        $this->preservePermissions = (bool) $bool;
202
    }
203
204
    /**
205
     * @param $bool
206
     */
207
    public function setPreservemode($bool)
208
    {
209
        $this->setPreservepermissions($bool);
210
    }
211
212
    /**
213
     * Set the include empty dirs flag. IntrospectionHelper takes care of
214
     * booleans in set* methods so we can assume that the right
215
     * value (boolean primitive) is coming in here.
216
     *
217
     * @param  boolean $bool Flag if empty dirs should be cpoied too
218
     * @return void
219
     */
220
    public function setIncludeEmptyDirs($bool)
221
    {
222
        $this->includeEmpty = (bool) $bool;
223
    }
224
225
    /**
226
     * Set the file. We have to manually take care of the
227
     * type that is coming due to limited type support in php
228
     * in and convert it manually if necessary.
229
     *
230
     * @param File $file The source file. Either a string or an PhingFile object
231
     *
232
     * @return void
233
     */
234 16
    public function setFile(File $file)
235
    {
236 16
        $this->file = $file;
237 16
    }
238
239
    /**
240
     * Set the toFile. We have to manually take care of the
241
     * type that is coming due to limited type support in php
242
     * in and convert it manually if necessary.
243
     *
244
     * @param File $file The dest file. Either a string or an PhingFile object
245
     *
246
     * @return void
247
     */
248 9
    public function setTofile(File $file)
249
    {
250 9
        $this->destFile = $file;
251 9
    }
252
253
    /**
254
     * Sets the mode to create destination directories with (ignored on Windows).
255
     * Default mode is taken from umask()
256
     *
257
     * @param integer $mode Octal mode
258
     *
259
     * @return void
260
     */
261
    public function setMode($mode)
262
    {
263
        $this->mode = (int) base_convert($mode, 8, 10);
264
    }
265
266
    /**
267
     * Set the toDir. We have to manually take care of the
268
     * type that is coming due to limited type support in php
269
     * in and convert it manually if necessary.
270
     *
271
     * @param File $dir The directory, either a string or an PhingFile object
272
     *
273
     * @return void
274
     */
275 51
    public function setTodir(File $dir)
276
    {
277 51
        $this->destDir = $dir;
278 51
    }
279
280 2
    public function setEnableMultipleMappings($enableMultipleMappings)
281
    {
282 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
283 2
    }
284
285
    public function isEnabledMultipleMappings()
286
    {
287
        return $this->enableMultipleMappings;
288
    }
289
290
    /**
291
     * Set the haltonerror attribute - when true, will
292
     * make the build fail when errors are detected.
293
     *
294
     * @param boolean $haltonerror Flag if the build should be stopped on errors
295
     *
296
     * @return void
297
     */
298 1
    public function setHaltonerror($haltonerror)
299
    {
300 1
        $this->haltonerror = (bool) $haltonerror;
301 1
    }
302
303
    /**
304
     * Nested creator, creates one Mapper for this task
305
     *
306
     * @return Mapper         The created Mapper type object
307
     * @throws BuildException
308
     */
309 3
    public function createMapper()
310
    {
311 3
        if ($this->mapperElement !== null) {
312
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
313
        }
314 3
        $this->mapperElement = new Mapper($this->project);
315
316 3
        return $this->mapperElement;
317
    }
318
319
    /**
320
     * The main entry point where everything gets in motion.
321
     *
322
     * @return true           on success
323
     * @throws BuildException
324
     */
325 59
    public function main()
326
    {
327 59
        $this->validateAttributes();
328
329 59
        if ($this->file !== null) {
330 15
            if ($this->file->exists()) {
331 14
                if ($this->destFile === null) {
332 7
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
333
                }
334
                if (
335 14
                    $this->overwrite === true
336 14
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
337
                ) {
338 12
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
339
                } else {
340 14
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
341
                }
342
            } else {
343
                // terminate build
344 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
345
            }
346
        }
347
348 59
        $project = $this->getProject();
349
350
        // process filelists
351 59
        foreach ($this->filelists as $fl) {
352 2
            $fromDir = $fl->getDir($project);
353 2
            $srcFiles = $fl->getFiles($project);
354 2
            $srcDirs = [$fl->getDir($project)];
355
356 2
            if (!$this->flatten && $this->mapperElement === null) {
357 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
358
            }
359
360 2
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
361
        }
362
363 59
        foreach ($this->dirsets as $dirset) {
364
            try {
365 1
                $ds = $dirset->getDirectoryScanner($project);
366 1
                $fromDir = $dirset->getDir($project);
367 1
                $srcDirs = $ds->getIncludedDirectories();
368
369 1
                $srcFiles = [];
370 1
                foreach ($srcDirs as $srcDir) {
371 1
                    $srcFiles[] = $srcDir;
372
                }
373
374
                if (
375 1
                    !$this->flatten &&
376 1
                    $this->mapperElement === null &&
377 1
                    $ds->isEverythingIncluded()
378
                ) {
379
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
380
                }
381
382 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
383
            } catch (BuildException $e) {
384
                if ($this->haltonerror === true) {
385
                    throw $e;
386
                }
387
388
                $this->logError($e->getMessage());
389
            }
390
        }
391
392
        // process filesets
393 59
        foreach ($this->filesets as $fs) {
394
            try {
395 44
                $ds = $fs->getDirectoryScanner($project);
396 40
                $fromDir = $fs->getDir($project);
397 40
                $srcFiles = $ds->getIncludedFiles();
398 40
                $srcDirs = $ds->getIncludedDirectories();
399
400
                if (
401 40
                    !$this->flatten
402 40
                    && $this->mapperElement === null
403 40
                    && $ds->isEverythingIncluded()
404
                ) {
405 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
406
                }
407
408 40
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
409 4
            } catch (BuildException $e) {
410 4
                if ($this->haltonerror == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
411 4
                    throw $e;
412
                }
413
414
                $this->logError($e->getMessage());
415
            }
416
        }
417
418
        // go and copy the stuff
419 55
        $this->doWork();
420
421 55
        if ($this->destFile !== null) {
422 16
            $this->destDir = null;
423
        }
424 55
    }
425
426
    /**
427
     * Validates attributes coming in from XML
428
     *
429
     * @return void
430
     * @throws BuildException
431
     */
432 58
    protected function validateAttributes()
433
    {
434 58
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
435
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
436
        }
437
438 58
        if ($this->destFile !== null && $this->destDir !== null) {
439
            throw new BuildException("Only one of destfile and destdir may be set.");
440
        }
441
442 58
        if ($this->destFile === null && $this->destDir === null) {
443
            throw new BuildException("One of destfile or destdir must be set.");
444
        }
445
446 58
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
447
            throw new BuildException("Use a fileset to copy directories.");
448
        }
449
450 58
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
451
            throw new BuildException("Cannot concatenate multiple files into a single file.");
452
        }
453
454 58
        if ($this->destFile !== null) {
455 8
            $this->destDir = new File($this->destFile->getParent());
456
        }
457 58
    }
458
459
    /**
460
     * Compares source files to destination files to see if they
461
     * should be copied.
462
     *
463
     * @param $fromDir
464
     * @param $toDir
465
     * @param $files
466
     * @param $dirs
467
     *
468
     * @return void
469
     */
470 43
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
471
    {
472
        /* mappers should be generic, so we get the mappers here and
473
        pass them on to builMap. This method is not redundan like it seems */
474 43
        $mapper = $this->getMapper();
475
476 43
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
477
478 43
        if ($this->includeEmpty) {
479 43
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
480
        }
481 43
    }
482
483 43
    private function getMapper()
484
    {
485 43
        $mapper = null;
486 43
        if ($this->mapperElement !== null) {
487 3
            $mapper = $this->mapperElement->getImplementation();
488 40
        } elseif ($this->flatten) {
489
            $mapper = new FlattenMapper();
490
        } else {
491 40
            $mapper = new IdentityMapper();
492
        }
493 43
        return $mapper;
494
    }
495
496
    /**
497
     * Builds a map of filenames (from->to) that should be copied
498
     *
499
     * @param $fromDir
500
     * @param $toDir
501
     * @param $names
502
     * @param FileNameMapper $mapper
503
     * @param $map
504
     *
505
     * @return void
506
     */
507 43
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
508
    {
509 43
        $toCopy = null;
510 43
        if ($this->overwrite) {
511 3
            $v = [];
512 3
            foreach ($names as $name) {
513 3
                $result = $mapper->main($name);
514 3
                if ($result !== null) {
515 3
                    $v[] = $name;
516
                }
517
            }
518 3
            $toCopy = $v;
519
        } else {
520 40
            $ds = new SourceFileScanner($this);
521 40
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
522
        }
523
524 43
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
525 40
            $src = new File($fromDir, $toCopy[$i]);
526 40
            $mapped = $mapper->main($toCopy[$i]);
527 40
            if (!$this->enableMultipleMappings) {
528 38
                $dest = new File($toDir, $mapped[0]);
529 38
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
530
            } else {
531 2
                $mappedFiles = [];
532
533 2
                foreach ($mapped as $mappedFile) {
534 2
                    if ($mappedFile === null) {
535
                        continue;
536
                    }
537 2
                    $dest = new File($toDir, $mappedFile);
538 2
                    $mappedFiles[] = $dest->getAbsolutePath();
539
                }
540 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
541
            }
542
        }
543 43
    }
544
545
    /**
546
     * Actually copies the files
547
     *
548
     * @return void
549
     * @throws BuildException
550
     */
551 49
    protected function doWork()
552
    {
553
554
        // These "slots" allow filters to retrieve information about the currently-being-process files
555 49
        $fromSlot = $this->getRegisterSlot("currentFromFile");
556 49
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
557
558 49
        $toSlot = $this->getRegisterSlot("currentToFile");
559 49
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
560
561 49
        $mapSize = count($this->fileCopyMap);
562 49
        $total = $mapSize;
563
564
        // handle empty dirs if appropriate
565 49
        if ($this->includeEmpty) {
566 49
            $count = 0;
567 49
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
568 4
                $s = new File((string) $srcdir);
569 4
                $d = new File((string) $destdir);
570 4
                if (!$d->exists()) {
571
                    // Setting source directory permissions to target
572
                    // (On permissions preservation, the target directory permissions
573
                    // will be inherited from the source directory, otherwise the 'mode'
574
                    // will be used)
575 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
576
577
                    // Directory creation with specific permission mode
578 4
                    if (!$d->mkdirs($dirMode)) {
579
                        $this->logError("Unable to create directory " . $d->__toString());
580
                    } else {
581 4
                        if ($this->preserveLMT) {
582
                            $d->setLastModified($s->lastModified());
583
                        }
584
585 4
                        $count++;
586
                    }
587
                }
588
            }
589 49
            if ($count > 0) {
590 4
                $this->log(
591 4
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
592
                );
593
            }
594
        }
595
596 49
        if ($mapSize == 0) {
597 4
            return;
598
        }
599
600 45
        $this->log(
601 45
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
602
        );
603
        // walks the map and actually copies the files
604 45
        $count = 0;
605 45
        foreach ($this->fileCopyMap as $from => $toFiles) {
606 45
            if (is_array($toFiles)) {
607 2
                foreach ($toFiles as $to) {
608 2
                    $this->copyToSingleDestination(
609 2
                        $from,
610
                        $to,
611
                        $fromSlot,
612
                        $fromBasenameSlot,
613
                        $toSlot,
614
                        $toBasenameSlot,
615
                        $count,
616
                        $total
617
                    );
618
                }
619
            } else {
620 43
                $this->copyToSingleDestination(
621 43
                    $from,
622
                    $toFiles,
623
                    $fromSlot,
624
                    $fromBasenameSlot,
625
                    $toSlot,
626
                    $toBasenameSlot,
627
                    $count,
628
                    $total
629
                );
630
            }
631
        }
632 45
    }
633
634
    /**
635
     * @param $from
636
     * @param $to
637
     * @param RegisterSlot $fromSlot
638
     * @param RegisterSlot $fromBasenameSlot
639
     * @param RegisterSlot $toSlot
640
     * @param RegisterSlot $toBasenameSlot
641
     * @param $count
642
     * @param $total
643
     */
644 45
    private function copyToSingleDestination(
645
        $from,
646
        $to,
647
        $fromSlot,
648
        $fromBasenameSlot,
649
        $toSlot,
650
        $toBasenameSlot,
651
        &$count,
652
        &$total
653
    ) {
654 45
        if ($from === $to) {
655
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
656
            $total--;
657
            return;
658
        }
659 45
        $this->log("From " . $from . " to " . $to, $this->verbosity);
660
        try { // try to copy file
661 45
            $fromFile = new File($from);
662 45
            $toFile = new File($to);
663
664 45
            $fromSlot->setValue($fromFile->getPath());
665 45
            $fromBasenameSlot->setValue($fromFile->getName());
666
667 45
            $toSlot->setValue($toFile->getPath());
668 45
            $toBasenameSlot->setValue($toFile->getName());
669
670 45
            $this->fileUtils->copyFile(
671 45
                $fromFile,
672
                $toFile,
673 45
                $this->getProject(),
674 45
                $this->overwrite,
675 45
                $this->preserveLMT,
676 45
                $this->filterChains,
677 45
                $this->mode,
678 45
                $this->preservePermissions,
679 45
                $this->granularity
680
            );
681
682 45
            $count++;
683
        } catch (IOException $ioe) {
684
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
685
        }
686 45
    }
687
688
    /**
689
     * @param string $message
690
     * @param null $location
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $location is correct as it would always require null to be passed?
Loading history...
691
     *
692
     * @throws BuildException
693
     */
694 1
    protected function logError($message, $location = null)
695
    {
696 1
        if ($this->haltonerror) {
697
            throw new BuildException($message, $location);
698
        }
699
700 1
        $this->log($message, Project::MSG_ERR);
701 1
    }
702
}
703