Passed
Push — master ( cb5cd1...da0f4b )
by Siad
10:24
created

PhingTask::initializeProject()   F

Complexity

Conditions 14
Paths 1020

Size

Total Lines 63
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 14.1777

Importance

Changes 0
Metric Value
cc 14
eloc 33
c 0
b 0
f 0
nc 1020
nop 0
dl 0
loc 63
ccs 28
cts 31
cp 0.9032
crap 14.1777
rs 2.1

How to fix   Long Method    Complexity   

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:

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
 * Task that invokes phing on another build file.
22
 *
23
 * Use this task, for example, if you have nested buildfiles in your project. Unlike
24
 * AntTask, PhingTask can even support filesets:
25
 *
26
 * <pre>
27
 *   <phing>
28
 *    <fileset dir="${srcdir}">
29
 *      <include name="** /build.xml" /> <!-- space added after ** is there because of PHP comment syntax -->
30
 *      <exclude name="build.xml" />
31
 *    </fileset>
32
 *   </phing>
33
 * </pre>
34
 *
35
 * @author  Hans Lellelid <[email protected]>
36
 * @package phing.tasks.system
37
 */
38
class PhingTask extends Task
39
{
40
    use FileSetAware;
41
42
    /**
43
     * the basedir where is executed the build file
44
     */
45
    private $dir;
46
47
    /**
48
     * build.xml (can be absolute) in this case dir will be ignored
49
     */
50
    private $phingFile;
51
52
    /**
53
     * the target to call if any
54
     */
55
    protected $newTarget;
56
57
    /**
58
     * should we inherit properties from the parent ?
59
     */
60
    private $inheritAll = true;
61
62
    /**
63
     * should we inherit references from the parent ?
64
     */
65
    private $inheritRefs = false;
66
67
    /**
68
     * the properties to pass to the new project
69
     */
70
    private $properties = [];
71
72
    /**
73
     * the references to pass to the new project
74
     */
75
    private $references = [];
76
77
    /**
78
     * The temporary project created to run the build file
79
     *
80
     * @var Project
81
     */
82
    private $newProject;
83
84
    /**
85
     * Fail the build process when the called build fails?
86
     */
87
    private $haltOnFailure = false;
88
89
    /**
90
     * @var OutputStream
91
     */
92
    private $out;
93
94
    /** @var string */
95
    private $output;
96
97 27
    public function __construct(Task $owner = null)
98
    {
99 27
        if ($owner !== null) {
100
            $this->bindToOwner($owner);
101
        }
102 27
        parent::__construct();
103 27
    }
104
105
    /**
106
     *  If true, abort the build process if there is a problem with or in the target build file.
107
     *  Defaults to false.
108
     *
109
     * @param bool $hof new value
110
     */
111 14
    public function setHaltOnFailure($hof)
112
    {
113 14
        $this->haltOnFailure = (bool) $hof;
114 14
    }
115
116
    /**
117
     * Creates a Project instance for the project to call.
118
     *
119
     * @return void
120
     */
121 27
    public function init()
122
    {
123 27
        $this->newProject = new Project();
124 27
        $tdf = $this->project->getTaskDefinitions();
125 27
        $this->newProject->addTaskDefinition("property", $tdf["property"]);
126 27
    }
127
128
    /**
129
     * Called in execute or createProperty if newProject is null.
130
     *
131
     * <p>This can happen if the same instance of this task is run
132
     * twice as newProject is set to null at the end of execute (to
133
     * save memory and help the GC).</p>
134
     *
135
     * <p>Sets all properties that have been defined as nested
136
     * property elements.</p>
137
     */
138 6
    private function reinit()
139
    {
140 6
        $this->init();
141
142 6
        $count = count($this->properties);
143 6
        for ($i = 0; $i < $count; $i++) {
144
            /**
145
             * @var PropertyTask $p
146
             */
147 6
            $p = $this->properties[$i];
148
            /**
149
             * @var PropertyTask $newP
150
             */
151 6
            $newP = $this->newProject->createTask("property");
152 6
            $newP->setName($p->getName());
153 6
            if ($p->getValue() !== null) {
154 6
                $newP->setValue($p->getValue());
155
            }
156 6
            if ($p->getFile() !== null) {
157
                $newP->setFile($p->getFile());
158
            }
159 6
            if ($p->getPrefix() !== null) {
160
                $newP->setPrefix($p->getPrefix());
161
            }
162 6
            if ($p->getRefid() !== null) {
163
                $newP->setRefid($p->getRefid());
164
            }
165 6
            if ($p->getEnvironment() !== null) {
166
                $newP->setEnvironment($p->getEnvironment());
167
            }
168 6
            if ($p->getUserProperty() !== null) {
169 6
                $newP->setUserProperty($p->getUserProperty());
170
            }
171 6
            $newP->setOverride($p->getOverride());
172 6
            $newP->setLogoutput($p->getLogoutput());
173 6
            $newP->setQuiet($p->getQuiet());
174
175 6
            $this->properties[$i] = $newP;
176
        }
177 6
    }
178
179
    /**
180
     * Main entry point for the task.
181
     *
182
     * @return void
183
     */
184 24
    public function main()
185
    {
186
        // Call Phing on the file set with the attribute "phingfile"
187 24
        if ($this->phingFile !== null || $this->dir !== null) {
188 24
            $this->processFile();
189
        }
190
191
        // if no filesets are given stop here; else process filesets
192 21
        if (!empty($this->filesets)) {
193
            // preserve old settings
194
            $savedDir = $this->dir;
195
            $savedPhingFile = $this->phingFile;
196
            $savedTarget = $this->newTarget;
197
198
            // set no specific target for files in filesets
199
            // [HL] I'm commenting this out; I don't know why this should not be supported!
200
            // $this->newTarget = null;
201
202
            foreach ($this->filesets as $fs) {
203
                $ds = $fs->getDirectoryScanner($this->project);
204
205
                $fromDir = $fs->getDir($this->project);
0 ignored issues
show
Unused Code introduced by
The assignment to $fromDir is dead and can be removed.
Loading history...
206
                $srcFiles = $ds->getIncludedFiles();
207
208
                foreach ($srcFiles as $fname) {
209
                    $f = new PhingFile($ds->getbasedir(), $fname);
210
                    $f = $f->getAbsoluteFile();
211
                    $this->phingFile = $f->getAbsolutePath();
212
                    $this->dir = $f->getParentFile();
213
                    $this->processFile(); // run Phing!
214
                }
215
            }
216
217
            // side effect free programming ;-)
218
            $this->dir = $savedDir;
219
            $this->phingFile = $savedPhingFile;
220
            $this->newTarget = $savedTarget;
221
222
            // [HL] change back to correct dir
223
            if ($this->dir !== null) {
224
                chdir($this->dir->getAbsolutePath());
225
            }
226
        }
227
228
        // Remove any dangling references to help the GC
229 21
        foreach ($this->properties as $property) {
230 11
            $property->setFallback(null);
231
        }
232 21
    }
233
234
    /**
235
     * Execute phing file.
236
     *
237
     * @throws BuildException
238
     */
239 24
    private function processFile(): void
240
    {
241 24
        $buildFailed = false;
242 24
        $savedDir = $this->dir;
243 24
        $savedPhingFile = $this->phingFile;
244 24
        $savedTarget = $this->newTarget;
245
246 24
        $savedBasedirAbsPath = null; // this is used to save the basedir *if* we change it
247
248
        try {
249 24
            $this->getNewProject();
250
251 24
            $this->initializeProject();
252
253 24
            if ($this->dir !== null) {
254 4
                $dirAbsPath = $this->dir->getAbsolutePath();
255
256
                // BE CAREFUL! -- when the basedir is changed for a project,
257
                // all calls to getAbsolutePath() on a relative-path dir will
258
                // be made relative to the project's basedir!  This means
259
                // that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!
260
261
                // We need to save the current project's basedir first.
262 4
                $savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();
263
264 4
                $this->newProject->setBasedir($this->dir);
265
266
                // Now we must reset $this->dir so that it continues to resolve to the same
267
                // path.
268 4
                $this->dir = new PhingFile($dirAbsPath);
269
270 4
                if ($savedDir !== null) { // has been set explicitly
271 4
                    $this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());
272
                }
273
            } else {
274
                // Since we're not changing the basedir here (for file resolution),
275
                // we don't need to worry about any side-effects in this scanrio.
276 21
                $this->dir = $this->getProject()->getBasedir();
277
            }
278
279 24
            $this->overrideProperties();
280 24
            $this->phingFile = $this->phingFile ?? 'build.xml';
281
282 24
            $fu = new FileUtils();
283 24
            $file = $fu->resolveFile($this->dir, $this->phingFile);
284 24
            $this->phingFile = $file->getAbsolutePath();
285
286 24
            $this->log(
287 24
                "Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'",
288 24
                Project::MSG_VERBOSE
289
            );
290
291 24
            $this->newProject->setUserProperty("phing.file", $this->phingFile);
292
293 24
            ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));
294
295 23
            if ($this->newTarget === null) {
296
                $this->newTarget = $this->newProject->getDefaultTarget();
297
            }
298
299
            // Are we trying to call the target in which we are defined?
300
            if (
301 23
                $this->newProject->getBaseDir() == $this->project->getBaseDir()
302 19
                && $this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file")
303 16
                && $this->getOwningTarget() !== null
304 16
                && $this->newTarget == $this->getOwningTarget()->getName()
305
            ) {
306 2
                throw new BuildException("phing task calling its own parent target");
307
            }
308
309 21
            $this->addReferences();
310 21
            $this->newProject->executeTarget($this->newTarget);
311 4
        } catch (Exception $e) {
312 4
            $buildFailed = true;
313 4
            $this->log($e->getMessage(), Project::MSG_ERR);
314 4
            if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {
315 4
                $lines = explode("\n", $e->getTraceAsString());
316 4
                foreach ($lines as $line) {
317 4
                    $this->log($line, Project::MSG_DEBUG);
318
                }
319
            }
320
            // important!!! continue on to perform cleanup tasks.
321 21
        } finally {
322
            // reset environment values to prevent side-effects.
323
324 24
            $this->newProject = null;
325 24
            $pkeys = array_keys($this->properties);
326 24
            foreach ($pkeys as $k) {
327 11
                $this->properties[$k]->setProject(null);
328
            }
329
330 24
            if ($this->output !== null && $this->out !== null) {
331 1
                $this->out->close();
332
            }
333
334 24
            $this->dir = $savedDir;
335 24
            $this->phingFile = $savedPhingFile;
336 24
            $this->newTarget = $savedTarget;
337
338
            // If the basedir for any project was changed, we need to set that back here.
339 24
            if ($savedBasedirAbsPath !== null) {
340 4
                chdir($savedBasedirAbsPath);
341
            }
342
343 24
            if ($this->haltOnFailure && $buildFailed) {
344 24
                throw new BuildException("Execution of the target buildfile failed. Aborting.");
345
            }
346
        }
347 21
    }
348
349
    /**
350
     * Get the (sub)-Project instance currently in use.
351
     *
352
     * @return Project
353
     */
354 24
    protected function getNewProject(): \Project
355
    {
356 24
        if ($this->newProject === null) {
357 6
            $this->reinit();
358
        }
359 24
        return $this->newProject;
360
    }
361
362
    /**
363
     * Configure the Project, i.e. make intance, attach build listeners
364
     * (copy from father project), add Task and Datatype definitions,
365
     * copy properties and references from old project if these options
366
     * are set via the attributes of the XML tag.
367
     *
368
     * Developer note:
369
     * This function replaces the old methods "init", "_reinit" and
370
     * "_initializeProject".
371
     */
372 24
    private function initializeProject()
373
    {
374 24
        $this->newProject->setInputHandler($this->project->getInputHandler());
375
376 24
        foreach ($this->project->getBuildListeners() as $listener) {
377 24
            $this->newProject->addBuildListener($listener);
378
        }
379
380
        /* Copy things from old project. Datatypes and Tasks are always
381
         * copied, properties and references only if specified so/not
382
         * specified otherwise in the XML definition.
383
         */
384
        // Add Datatype definitions
385 24
        foreach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {
386 24
            $this->newProject->addDataTypeDefinition($typeName, $typeClass);
387
        }
388
389
        // Add Task definitions
390 24
        foreach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {
391 24
            if ($taskClass == "propertytask") {
392
                // we have already added this taskdef in init()
393
                continue;
394
            }
395 24
            $this->newProject->addTaskDefinition($taskName, $taskClass);
396
        }
397
398 24
        if ($this->output !== null) {
399
            try {
400 1
                if ($this->dir !== null) {
401 1
                    $outfile = (new FileUtils())->resolveFile($this->dir, $this->output);
402
                } else {
403 1
                    $outfile = $this->getProject()->resolveFile($this->output);
404
                }
405 1
                $this->out = new FileOutputStream($outfile);
406 1
                $logger = new DefaultLogger();
407 1
                $logger->setMessageOutputLevel(Project::MSG_INFO);
408 1
                $logger->setOutputStream($this->out);
409 1
                $logger->setErrorStream($this->out);
410 1
                $this->newProject->addBuildListener($logger);
411
            } catch (Exception $ex) {
412
                $this->log("Phing: Can't set output to " . $this->output);
413
            }
414
        }
415
416
        // set user-defined properties
417 24
        $this->project->copyUserProperties($this->newProject);
418
419 24
        if (!$this->inheritAll) {
420
            // set System built-in properties separately,
421
            // b/c we won't inherit them.
422 13
            $this->newProject->setSystemProperties();
423
        } else {
424
            // set all properties from calling project
425 12
            $properties = $this->project->getProperties();
426 12
            foreach ($properties as $name => $value) {
427 12
                if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {
428
                    // basedir and phing.file get special treatment in main()
429 12
                    continue;
430
                }
431
                // don't re-set user properties, avoid the warning message
432 12
                if ($this->newProject->getProperty($name) === null) {
433
                    // no user property
434 12
                    $this->newProject->setNewProperty($name, $value);
435
                }
436
            }
437
        }
438 24
    }
439
440
    /**
441
     * Override the properties in the new project with the one
442
     * explicitly defined as nested elements here.
443
     *
444
     * @return void
445
     * @throws BuildException
446
     */
447 24
    private function overrideProperties()
448
    {
449 24
        foreach (array_keys($this->properties) as $i) {
450 11
            $p = $this->properties[$i];
451 11
            $p->setProject($this->newProject);
452 11
            $p->main();
453
        }
454 24
        $this->project->copyInheritedProperties($this->newProject);
455 24
    }
456
457
    /**
458
     * Add the references explicitly defined as nested elements to the
459
     * new project.  Also copy over all references that don't override
460
     * existing references in the new project if inheritrefs has been
461
     * requested.
462
     *
463
     * @return void
464
     * @throws BuildException
465
     */
466 21
    private function addReferences()
467
    {
468
469
        // parent project references
470 21
        $projReferences = $this->project->getReferences();
471
472 21
        $newReferences = $this->newProject->getReferences();
473
474 21
        $subprojRefKeys = [];
475
476 21
        if (count($this->references) > 0) {
477
            for ($i = 0, $count = count($this->references); $i < $count; $i++) {
478
                /** @var Reference $ref */
479
                $ref = $this->references[$i];
480
                $refid = $ref->getRefId();
481
482
                if ($refid === null) {
483
                    throw new BuildException('the refid attribute is required for reference elements');
484
                }
485
                if (!isset($projReferences[$refid])) {
486
                    $this->log("Parent project doesn't contain any reference '" . $refid . "'", Project::MSG_WARN);
487
                    continue;
488
                }
489
490
                $subprojRefKeys[] = $refid;
491
                //thisReferences.remove(refid);
492
                $toRefid = $ref->getToRefid();
0 ignored issues
show
Bug introduced by
The method getToRefid() does not exist on Reference. Did you maybe mean getRefId()? ( Ignorable by Annotation )

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

492
                /** @scrutinizer ignore-call */ 
493
                $toRefid = $ref->getToRefid();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
493
                if ($toRefid === null) {
494
                    $toRefid = $refid;
495
                }
496
                $this->copyReference($refid, $toRefid);
497
            }
498
        }
499
500
        // Now add all references that are not defined in the
501
        // subproject, if inheritRefs is true
502 21
        if ($this->inheritRefs) {
503
            // get the keys that are were not used by the subproject
504 2
            $unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);
505
506 2
            foreach ($unusedRefKeys as $key) {
507 2
                if (isset($newReferences[$key])) {
508 2
                    continue;
509
                }
510 2
                $this->copyReference($key, $key);
511
            }
512
        }
513 21
    }
514
515
    /**
516
     * Try to clone and reconfigure the object referenced by oldkey in
517
     * the parent project and add it to the new project with the key
518
     * newkey.
519
     *
520
     * <p>If we cannot clone it, copy the referenced object itself and
521
     * keep our fingers crossed.</p>
522
     *
523
     * @param  string $oldKey
524
     * @param  string $newKey
525
     * @throws BuildException
526
     * @return void
527
     */
528 2
    private function copyReference($oldKey, $newKey)
529
    {
530 2
        $orig = $this->project->getReference($oldKey);
531 2
        if ($orig === null) {
532
            $this->log(
533
                "No object referenced by " . $oldKey . ". Can't copy to "
534
                . $newKey,
535
                Project::MSG_WARN
536
            );
537
538
            return;
539
        }
540
541 2
        $copy = clone $orig;
542
543 2
        if ($copy instanceof ProjectComponent) {
544 2
            $copy->setProject($this->newProject);
545 2
        } elseif (in_array('setProject', get_class_methods(get_class($copy)))) {
546
            $copy->setProject($this->newProject);
547 2
        } elseif (!($copy instanceof Project)) {
548
            // don't copy the old "Project" itself
549
            $msg = "Error setting new project instance for "
550
                . "reference with id " . $oldKey;
551
            throw new BuildException($msg);
552
        }
553
554 2
        $this->newProject->addReference($newKey, $copy);
555 2
    }
556
557
    /**
558
     * If true, pass all properties to the new phing project.
559
     * Defaults to true.
560
     *
561
     * @param $value
562
     */
563 20
    public function setInheritAll($value)
564
    {
565 20
        $this->inheritAll = (bool) $value;
566 20
    }
567
568
    /**
569
     * If true, pass all references to the new phing project.
570
     * Defaults to false.
571
     *
572
     * @param $value
573
     */
574 12
    public function setInheritRefs($value)
575
    {
576 12
        $this->inheritRefs = (bool) $value;
577 12
    }
578
579
    /**
580
     * The directory to use as a base directory for the new phing project.
581
     * Defaults to the current project's basedir, unless inheritall
582
     * has been set to false, in which case it doesn't have a default
583
     * value. This will override the basedir setting of the called project.
584
     *
585
     * @param PhingFile $d
586
     */
587 4
    public function setDir(PhingFile $d): void
588
    {
589 4
        $this->dir = $d;
590 4
    }
591
592
    /**
593
     * The build file to use.
594
     * Defaults to "build.xml". This file is expected to be a filename relative
595
     * to the dir attribute given.
596
     *
597
     * @param $s
598
     */
599 24
    public function setPhingFile($s)
600
    {
601
        // it is a string and not a file to handle relative/absolute
602
        // otherwise a relative file will be resolved based on the current
603
        // basedir.
604 24
        $this->phingFile = $s;
605 24
    }
606
607
    /**
608
     * Alias function for setPhingfile
609
     *
610
     * @param $s
611
     */
612
    public function setBuildfile($s)
613
    {
614
        $this->setPhingFile($s);
615
    }
616
617
    /**
618
     * The target of the new Phing project to execute.
619
     * Defaults to the new project's default target.
620
     *
621
     * @param string $s
622
     */
623 25
    public function setTarget(string $s)
624
    {
625 25
        if ('' === $s) {
626 1
            throw new BuildException("target attribute must not be empty");
627
        }
628
629 24
        $this->newTarget = $s;
630 24
    }
631
632
    /**
633
     * Set the filename to write the output to. This is relative to the value
634
     * of the dir attribute if it has been set or to the base directory of the
635
     * current project otherwise.
636
     * @param string $outputFile the name of the file to which the output should go.
637
     */
638 1
    public function setOutput(string $outputFile): void
639
    {
640 1
        $this->output = $outputFile;
641 1
    }
642
643
    /**
644
     * Property to pass to the new project.
645
     * The property is passed as a 'user property'
646
     */
647 11
    public function createProperty()
648
    {
649 11
        $p = new PropertyTask();
650 11
        $p->setFallback($this->getNewProject());
651 11
        $p->setUserProperty(true);
652 11
        $this->properties[] = $p;
653
654 11
        return $p;
655
    }
656
657
    /**
658
     * Reference element identifying a data type to carry
659
     * over to the new project.
660
     *
661
     * @param PhingReference $ref
662
     */
663 1
    public function addReference(PhingReference $ref)
664
    {
665 1
        $this->references[] = $ref;
666 1
    }
667
}
668