CopyTask::copyToSingleDestination()   A
last analyzed

Complexity

Conditions 3
Paths 9

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 43
rs 9.52
c 0
b 0
f 0
cc 3
nc 9
nop 8

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, $fromDir, /** @scrutinizer ignore-type */ $toDir, $mapper);
Loading history...
Bug introduced by
$fromDir of type string is incompatible with the type Phing\Io\File expected by parameter $srcDir of Phing\Io\SourceFileScanner::restrict(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, /** @scrutinizer ignore-type */ $fromDir, $toDir, $mapper);
Loading history...
594
        }
595
596
        for ($i = 0, $_i = count($toCopy); $i < $_i; ++$i) {
597
            $src = new File($fromDir, $toCopy[$i]);
598
            $mapped = $mapper->main($toCopy[$i]);
599
            if (!$this->enableMultipleMappings) {
600
                $dest = new File($toDir, $mapped[0]);
601
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
602
            } else {
603
                $mappedFiles = [];
604
605
                foreach ($mapped as $mappedFile) {
606
                    if (null === $mappedFile) {
607
                        continue;
608
                    }
609
                    $dest = new File($toDir, $mappedFile);
610
                    $mappedFiles[] = $dest->getAbsolutePath();
611
                }
612
                $map[$src->getAbsolutePath()] = $mappedFiles;
613
            }
614
        }
615
    }
616
617
    /**
618
     * @param string       $from
619
     * @param string       $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param int          $count
625
     * @param int          $total
626
     */
627
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637
        if ($from === $to) {
638
            $this->log('Skipping self-copy of ' . $from, $this->verbosity);
639
            --$total;
640
641
            return;
642
        }
643
        $this->log('From ' . $from . ' to ' . $to, $this->verbosity);
644
645
        try { // try to copy file
646
            $fromFile = new File($from);
647
            $toFile = new File($to);
648
649
            $fromSlot->setValue($fromFile->getPath());
650
            $fromBasenameSlot->setValue($fromFile->getName());
651
652
            $toSlot->setValue($toFile->getPath());
653
            $toBasenameSlot->setValue($toFile->getName());
654
655
            $this->fileUtils->copyFile(
656
                $fromFile,
657
                $toFile,
658
                $this->getProject(),
659
                $this->overwrite,
660
                $this->preserveLMT,
661
                $this->filterChains,
662
                $this->mode,
663
                $this->preservePermissions,
664
                $this->granularity
665
            );
666
667
            ++$count;
668
        } catch (IOException $ioe) {
669
            $this->logError('Failed to copy ' . $from . ' to ' . $to . ': ' . $ioe->getMessage());
670
        }
671
    }
672
}
673