Passed
Push — master ( fedd27...f10ad5 )
by Michiel
11:13
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
/**
21
 * A phing copy task.  Copies a file or directory to a new file
22
 * or directory.  Files are only copied if the source file is newer
23
 * than the destination file, or when the destination file does not
24
 * exist. It is possible to explicitly overwrite existing files.
25
 *
26
 * @author Andreas Aderhold, [email protected]
27
 *
28
 * @package phing.tasks.system
29
 */
30
class CopyTask extends Task
31
{
32
    use ResourceAware;
33
    use FilterChainAware;
34
35
    /**
36
     * @var PhingFile
37
     */
38
    protected $file = null; // the source file (from xml attribute)
39
40
    /**
41
     * @var PhingFile
42
     */
43
    protected $destFile = null; // the destiantion file (from xml attribute)
44
45
    /**
46
     * @var PhingFile
47
     */
48
    protected $destDir = null; // the destination dir (from xml attribute)
49
50
    protected $overwrite = false; // overwrite destination (from xml attribute)
51
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
52
    protected $preservePermissions = true; // sync permissions (from xml attribute)
53
    protected $includeEmpty = true; // include empty dirs? (from XML)
54
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
55
56
    /**
57
     * @var Mapper
58
     */
59
    protected $mapperElement = null;
60
61
    protected $fileCopyMap = []; // asoc array containing mapped file names
62
    protected $dirCopyMap = []; // asoc array containing mapped file names
63
    protected $completeDirMap = []; // asoc array containing complete dir names
64
65
    /**
66
     * @var FileUtils
67
     */
68
    protected $fileUtils = null; // a instance of fileutils
69
70
    protected $verbosity = Project::MSG_VERBOSE;
71
72
    /**
73
     * @var int $mode
74
     */
75
    protected $mode = 0; // mode to create directories with
76
77
    /**
78
     * @var bool $haltonerror
79
     */
80
    protected $haltonerror = true; // stop build on errors
81
82
    protected $enableMultipleMappings = false;
83
84
    /** @var int $granularity */
85
    protected $granularity = 0;
86
87
    /**
88
     * Sets up this object internal stuff.
89
     * i.e. the Fileutils instance and default mode.
90
     */
91 42
    public function __construct()
92
    {
93 42
        parent::__construct();
94 42
        $this->fileUtils = new FileUtils();
95 42
        $this->mode = 0777 - umask();
96 42
    }
97
98
    /**
99
     * Set the number of seconds leeway to give before deciding a
100
     * target is out of date.
101
     *
102
     * @param int $granularity the granularity used to decide if a target is out of date.
103
     */
104 2
    public function setGranularity(int $granularity): void
105
    {
106 2
        $this->granularity = $granularity;
107 2
    }
108
109
    /**
110
     * Set the overwrite flag. IntrospectionHelper takes care of
111
     * booleans in set* methods so we can assume that the right
112
     * value (boolean primitive) is coming in here.
113
     *
114
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
115
     *
116
     * @return void
117
     */
118 9
    public function setOverwrite($bool)
119
    {
120 9
        $this->overwrite = (bool) $bool;
121 9
    }
122
123
    /**
124
     * Set whether files copied from directory trees will be "flattened"
125
     * into a single directory.  If there are multiple files with
126
     * the same name in the source directory tree, only the first
127
     * file will be copied into the "flattened" directory, unless
128
     * the forceoverwrite attribute is true.
129
     *
130
     * @param bool $flatten if true flatten the destination directory. Default
131
     *                is false.
132
     */
133
    public function setFlatten($flatten)
134
    {
135
        $this->flatten = $flatten;
136
    }
137
138
    /**
139
     * Used to force listing of all names of copied files.
140
     *
141
     * @param boolean $verbosity
142
     */
143 1
    public function setVerbose($verbosity)
144
    {
145 1
        if ($verbosity) {
146 1
            $this->verbosity = Project::MSG_INFO;
147
        } else {
148
            $this->verbosity = Project::MSG_VERBOSE;
149
        }
150 1
    }
151
152
    /**
153
     * @see CopyTask::setPreserveLastModified
154
     * @param $bool
155
     */
156
    public function setTstamp($bool)
157
    {
158
        $this->setPreserveLastModified($bool);
159
    }
160
161
    /**
162
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
163
     * booleans in set* methods so we can assume that the right
164
     * value (boolean primitive) is coming in here.
165
     *
166
     * @param  boolean $bool Preserve the timestamp on the destination file
167
     * @return void
168
     */
169 1
    public function setPreserveLastModified($bool)
170
    {
171 1
        $this->preserveLMT = (bool) $bool;
172 1
    }
173
174
    /**
175
     * Set the preserve permissions flag. IntrospectionHelper takes care of
176
     * booleans in set* methods so we can assume that the right
177
     * value (boolean primitive) is coming in here.
178
     *
179
     * @param  boolean $bool Preserve the timestamp on the destination file
180
     * @return void
181
     */
182
    public function setPreservepermissions($bool)
183
    {
184
        $this->preservePermissions = (bool) $bool;
185
    }
186
187
    /**
188
     * @param $bool
189
     */
190
    public function setPreservemode($bool)
191
    {
192
        $this->setPreservepermissions($bool);
193
    }
194
195
    /**
196
     * Set the include empty dirs flag. IntrospectionHelper takes care of
197
     * booleans in set* methods so we can assume that the right
198
     * value (boolean primitive) is coming in here.
199
     *
200
     * @param  boolean $bool Flag if empty dirs should be cpoied too
201
     * @return void
202
     */
203
    public function setIncludeEmptyDirs($bool)
204
    {
205
        $this->includeEmpty = (bool) $bool;
206
    }
207
208
    /**
209
     * Set the file. We have to manually take care of the
210
     * type that is coming due to limited type support in php
211
     * in and convert it manually if necessary.
212
     *
213
     * @param PhingFile $file The source file. Either a string or an PhingFile object
214
     *
215
     * @return void
216
     */
217 17
    public function setFile(PhingFile $file)
218
    {
219 17
        $this->file = $file;
220 17
    }
221
222
    /**
223
     * Set the toFile. We have to manually take care of the
224
     * type that is coming due to limited type support in php
225
     * in and convert it manually if necessary.
226
     *
227
     * @param PhingFile $file The dest file. Either a string or an PhingFile object
228
     *
229
     * @return void
230
     */
231 9
    public function setTofile(PhingFile $file)
232
    {
233 9
        $this->destFile = $file;
234 9
    }
235
236
    /**
237
     * Sets the mode to create destination directories with (ignored on Windows).
238
     * Default mode is taken from umask()
239
     *
240
     * @param integer $mode Octal mode
241
     *
242
     * @return void
243
     */
244
    public function setMode($mode)
245
    {
246
        $this->mode = (int) base_convert($mode, 8, 10);
247
    }
248
249
    /**
250
     * Set the toDir. We have to manually take care of the
251
     * type that is coming due to limited type support in php
252
     * in and convert it manually if necessary.
253
     *
254
     * @param PhingFile $dir The directory, either a string or an PhingFile object
255
     *
256
     * @return void
257
     */
258 33
    public function setTodir(PhingFile $dir)
259
    {
260 33
        $this->destDir = $dir;
261 33
    }
262
263 2
    public function setEnableMultipleMappings($enableMultipleMappings)
264
    {
265 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
266 2
    }
267
268
    public function isEnabledMultipleMappings()
269
    {
270
        return $this->enableMultipleMappings;
271
    }
272
273
    /**
274
     * Set the haltonerror attribute - when true, will
275
     * make the build fail when errors are detected.
276
     *
277
     * @param boolean $haltonerror Flag if the build should be stopped on errors
278
     *
279
     * @return void
280
     */
281 1
    public function setHaltonerror($haltonerror)
282
    {
283 1
        $this->haltonerror = (bool) $haltonerror;
284 1
    }
285
286
    /**
287
     * Nested creator, creates one Mapper for this task
288
     *
289
     * @return Mapper         The created Mapper type object
290
     * @throws BuildException
291
     */
292 3
    public function createMapper()
293
    {
294 3
        if ($this->mapperElement !== null) {
295
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
296
        }
297 3
        $this->mapperElement = new Mapper($this->project);
298
299 3
        return $this->mapperElement;
300
    }
301
302
    /**
303
     * The main entry point where everything gets in motion.
304
     *
305
     * @return true           on success
306
     * @throws BuildException
307
     */
308 42
    public function main()
309
    {
310 42
        $this->validateAttributes();
311
312 42
        if ($this->file !== null) {
313 16
            if ($this->file->exists()) {
314 15
                if ($this->destFile === null) {
315 8
                    $this->destFile = new PhingFile($this->destDir, (string) $this->file->getName());
316
                }
317
                if (
318 15
                    $this->overwrite === true
319 8
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
320
                ) {
321 13
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
322
                } else {
323 2
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
324
                }
325
            } else {
326
                // terminate build
327 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
328
            }
329
        }
330
331 42
        $project = $this->getProject();
332
333
        // process filelists
334 42
        foreach ($this->filelists as $fl) {
335 2
            $fromDir = $fl->getDir($project);
336 2
            $srcFiles = $fl->getFiles($project);
337 2
            $srcDirs = [$fl->getDir($project)];
338
339 2
            if (!$this->flatten && $this->mapperElement === null) {
340 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
341
            }
342
343 2
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
344
        }
345
346 42
        foreach ($this->dirsets as $dirset) {
347
            try {
348 1
                $ds = $dirset->getDirectoryScanner($project);
349 1
                $fromDir = $dirset->getDir($project);
350 1
                $srcDirs = $ds->getIncludedDirectories();
351
352 1
                $srcFiles = [];
353 1
                foreach ($srcDirs as $srcDir) {
354 1
                    $srcFiles[] = $srcDir;
355
                }
356
357
                if (
358 1
                    !$this->flatten &&
359 1
                    $this->mapperElement === null &&
360 1
                    $ds->isEverythingIncluded()
361
                ) {
362
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
363
                }
364
365 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
366
            } catch (BuildException $e) {
367
                if ($this->haltonerror === true) {
368
                    throw $e;
369
                }
370
371
                $this->logError($e->getMessage());
372
            }
373
        }
374
375
        // process filesets
376 42
        foreach ($this->filesets as $fs) {
377
            try {
378 26
                $ds = $fs->getDirectoryScanner($project);
379 26
                $fromDir = $fs->getDir($project);
380 26
                $srcFiles = $ds->getIncludedFiles();
381 26
                $srcDirs = $ds->getIncludedDirectories();
382
383
                if (
384 26
                    !$this->flatten
385 26
                    && $this->mapperElement === null
386 23
                    && $ds->isEverythingIncluded()
387
                ) {
388 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
389
                }
390
391 26
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
392
            } catch (BuildException $e) {
393
                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...
394
                    throw $e;
395
                }
396
397
                $this->logError($e->getMessage());
398
            }
399
        }
400
401
        // go and copy the stuff
402 42
        $this->doWork();
403
404 42
        if ($this->destFile !== null) {
405 17
            $this->destDir = null;
406
        }
407 42
    }
408
409
    /**
410
     * Validates attributes coming in from XML
411
     *
412
     * @return void
413
     * @throws BuildException
414
     */
415 41
    protected function validateAttributes()
416
    {
417 41
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
418
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
419
        }
420
421 41
        if ($this->destFile !== null && $this->destDir !== null) {
422
            throw new BuildException("Only one of destfile and destdir may be set.");
423
        }
424
425 41
        if ($this->destFile === null && $this->destDir === null) {
426
            throw new BuildException("One of destfile or destdir must be set.");
427
        }
428
429 41
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
430
            throw new BuildException("Use a fileset to copy directories.");
431
        }
432
433 41
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
434
            throw new BuildException("Cannot concatenate multiple files into a single file.");
435
        }
436
437 41
        if ($this->destFile !== null) {
438 8
            $this->destDir = new PhingFile($this->destFile->getParent());
439
        }
440 41
    }
441
442
    /**
443
     * Compares source files to destination files to see if they
444
     * should be copied.
445
     *
446
     * @param $fromDir
447
     * @param $toDir
448
     * @param $files
449
     * @param $dirs
450
     *
451
     * @return void
452
     */
453 29
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
454
    {
455
        /* mappers should be generic, so we get the mappers here and
456
        pass them on to builMap. This method is not redundan like it seems */
457 29
        $mapper = $this->getMapper();
458
459 29
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
460
461 29
        if ($this->includeEmpty) {
462 29
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
463
        }
464 29
    }
465
466 29
    private function getMapper()
467
    {
468 29
        $mapper = null;
469 29
        if ($this->mapperElement !== null) {
470 3
            $mapper = $this->mapperElement->getImplementation();
471 26
        } elseif ($this->flatten) {
472
            $mapper = new FlattenMapper();
473
        } else {
474 26
            $mapper = new IdentityMapper();
475
        }
476 29
        return $mapper;
477
    }
478
479
    /**
480
     * Builds a map of filenames (from->to) that should be copied
481
     *
482
     * @param $fromDir
483
     * @param $toDir
484
     * @param $names
485
     * @param FileNameMapper $mapper
486
     * @param $map
487
     *
488
     * @return void
489
     */
490 29
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
491
    {
492 29
        $toCopy = null;
493 29
        if ($this->overwrite) {
494 1
            $v = [];
495 1
            foreach ($names as $name) {
496 1
                $result = $mapper->main($name);
497 1
                if ($result !== null) {
498 1
                    $v[] = $name;
499
                }
500
            }
501 1
            $toCopy = $v;
502
        } else {
503 28
            $ds = new SourceFileScanner($this);
504 28
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
505
        }
506
507 29
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
508 26
            $src = new PhingFile($fromDir, $toCopy[$i]);
509 26
            $mapped = $mapper->main($toCopy[$i]);
510 26
            if (!$this->enableMultipleMappings) {
511 24
                $dest = new PhingFile($toDir, $mapped[0]);
512 24
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
513
            } else {
514 2
                $mappedFiles = [];
515
516 2
                foreach ($mapped as $mappedFile) {
517 2
                    if ($mappedFile === null) {
518
                        continue;
519
                    }
520 2
                    $dest = new PhingFile($toDir, $mappedFile);
521 2
                    $mappedFiles[] = $dest->getAbsolutePath();
522
                }
523 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
524
            }
525
        }
526 29
    }
527
528
    /**
529
     * Actually copies the files
530
     *
531
     * @return void
532
     * @throws BuildException
533
     */
534 36
    protected function doWork()
535
    {
536
537
        // These "slots" allow filters to retrieve information about the currently-being-process files
538 36
        $fromSlot = $this->getRegisterSlot("currentFromFile");
539 36
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
540
541 36
        $toSlot = $this->getRegisterSlot("currentToFile");
542 36
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
543
544 36
        $mapSize = count($this->fileCopyMap);
545 36
        $total = $mapSize;
546
547
        // handle empty dirs if appropriate
548 36
        if ($this->includeEmpty) {
549 36
            $count = 0;
550 36
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
551 4
                $s = new PhingFile((string) $srcdir);
552 4
                $d = new PhingFile((string) $destdir);
553 4
                if (!$d->exists()) {
554
                    // Setting source directory permissions to target
555
                    // (On permissions preservation, the target directory permissions
556
                    // will be inherited from the source directory, otherwise the 'mode'
557
                    // will be used)
558 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
559
560
                    // Directory creation with specific permission mode
561 4
                    if (!$d->mkdirs($dirMode)) {
562
                        $this->logError("Unable to create directory " . $d->__toString());
563
                    } else {
564 4
                        if ($this->preserveLMT) {
565
                            $d->setLastModified($s->lastModified());
566
                        }
567
568 4
                        $count++;
569
                    }
570
                }
571
            }
572 36
            if ($count > 0) {
573 4
                $this->log(
574 4
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
575
                );
576
            }
577
        }
578
579 36
        if ($mapSize == 0) {
580 4
            return;
581
        }
582
583 32
        $this->log(
584 32
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
585
        );
586
        // walks the map and actually copies the files
587 32
        $count = 0;
588 32
        foreach ($this->fileCopyMap as $from => $toFiles) {
589 32
            if (is_array($toFiles)) {
590 2
                foreach ($toFiles as $to) {
591 2
                    $this->copyToSingleDestination(
592 2
                        $from,
593
                        $to,
594
                        $fromSlot,
595
                        $fromBasenameSlot,
596
                        $toSlot,
597
                        $toBasenameSlot,
598
                        $count,
599
                        $total
600
                    );
601
                }
602
            } else {
603 30
                $this->copyToSingleDestination(
604 30
                    $from,
605
                    $toFiles,
606
                    $fromSlot,
607
                    $fromBasenameSlot,
608
                    $toSlot,
609
                    $toBasenameSlot,
610
                    $count,
611
                    $total
612
                );
613
            }
614
        }
615 32
    }
616
617
    /**
618
     * @param $from
619
     * @param $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param $count
625
     * @param $total
626
     */
627 32
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 32
        if ($from === $to) {
638
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
639
            $total--;
640
            return;
641
        }
642 32
        $this->log("From " . $from . " to " . $to, $this->verbosity);
643
        try { // try to copy file
644 32
            $fromFile = new PhingFile($from);
645 32
            $toFile = new PhingFile($to);
646
647 32
            $fromSlot->setValue($fromFile->getPath());
648 32
            $fromBasenameSlot->setValue($fromFile->getName());
649
650 32
            $toSlot->setValue($toFile->getPath());
651 32
            $toBasenameSlot->setValue($toFile->getName());
652
653 32
            $this->fileUtils->copyFile(
654 32
                $fromFile,
655
                $toFile,
656 32
                $this->getProject(),
657 32
                $this->overwrite,
658 32
                $this->preserveLMT,
659 32
                $this->filterChains,
660 32
                $this->mode,
661 32
                $this->preservePermissions,
662 32
                $this->granularity
663
            );
664
665 32
            $count++;
666
        } catch (IOException $ioe) {
667
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
668
        }
669 32
    }
670
671
    /**
672
     * @param string $message
673
     * @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...
674
     *
675
     * @throws BuildException
676
     */
677 1
    protected function logError($message, $location = null)
678
    {
679 1
        if ($this->haltonerror) {
680
            throw new BuildException($message, $location);
681
        }
682
683 1
        $this->log($message, Project::MSG_ERR);
684 1
    }
685
}
686