Completed
Push — master ( 9cf089...a49cfd )
by Siad
12:42
created

PhingTask   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 592
Duplicated Lines 0 %

Test Coverage

Coverage 68.95%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 205
c 3
b 0
f 0
dl 0
loc 592
ccs 151
cts 219
cp 0.6895
rs 2.56
wmc 73

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setPhingFile() 0 6 1
A setHaltOnFailure() 0 3 1
A addReference() 0 3 1
B main() 0 48 8
B reinit() 0 37 8
A init() 0 5 1
A setInheritAll() 0 3 1
A setBuildfile() 0 3 1
A createProperty() 0 8 1
A setInheritRefs() 0 3 1
A getNewProject() 0 6 2
B addReferences() 0 51 9
A overrideProperties() 0 8 2
B initializeProject() 0 45 11
A copyReference() 0 27 5
F processFile() 0 105 16
A setDir() 0 6 2
A setTarget() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like PhingTask often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PhingTask, and based on these observations, apply Extract Interface, too.

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
     *  If true, abort the build process if there is a problem with or in the target build file.
91
     *  Defaults to false.
92
     *
93
     * @param boolean $hof new value
94
     */
95 14
    public function setHaltOnFailure($hof)
96
    {
97 14
        $this->haltOnFailure = (bool) $hof;
98 14
    }
99
100
    /**
101
     * Creates a Project instance for the project to call.
102
     *
103
     * @return void
104
     */
105 14
    public function init()
106
    {
107 14
        $this->newProject = new Project();
108 14
        $tdf = $this->project->getTaskDefinitions();
109 14
        $this->newProject->addTaskDefinition("property", $tdf["property"]);
110 14
    }
111
112
    /**
113
     * Called in execute or createProperty if newProject is null.
114
     *
115
     * <p>This can happen if the same instance of this task is run
116
     * twice as newProject is set to null at the end of execute (to
117
     * save memory and help the GC).</p>
118
     *
119
     * <p>Sets all properties that have been defined as nested
120
     * property elements.</p>
121
     */
122 6
    private function reinit()
123
    {
124 6
        $this->init();
125 6
        $count = count($this->properties);
126 6
        for ($i = 0; $i < $count; $i++) {
127
            /**
128
             * @var PropertyTask $p
129
             */
130 6
            $p = $this->properties[$i];
131
            /**
132
             * @var PropertyTask $newP
133
             */
134 6
            $newP = $this->newProject->createTask("property");
135 6
            $newP->setName($p->getName());
136 6
            if ($p->getValue() !== null) {
137 6
                $newP->setValue($p->getValue());
138
            }
139 6
            if ($p->getFile() !== null) {
140
                $newP->setFile($p->getFile());
141
            }
142 6
            if ($p->getPrefix() !== null) {
143
                $newP->setPrefix($p->getPrefix());
144
            }
145 6
            if ($p->getRefid() !== null) {
146
                $newP->setRefid($p->getRefid());
147
            }
148 6
            if ($p->getEnvironment() !== null) {
149
                $newP->setEnvironment($p->getEnvironment());
150
            }
151 6
            if ($p->getUserProperty() !== null) {
152 6
                $newP->setUserProperty($p->getUserProperty());
153
            }
154 6
            $newP->setOverride($p->getOverride());
155 6
            $newP->setLogoutput($p->getLogoutput());
156 6
            $newP->setQuiet($p->getQuiet());
157
158 6
            $this->properties[$i] = $newP;
159
        }
160 6
    }
161
162
    /**
163
     * Main entry point for the task.
164
     *
165
     * @return void
166
     */
167 11
    public function main()
168
    {
169
170
        // Call Phing on the file set with the attribute "phingfile"
171 11
        if ($this->phingFile !== null or $this->dir !== null) {
172 11
            $this->processFile();
173
        }
174
175
        // if no filesets are given stop here; else process filesets
176 8
        if (!empty($this->filesets)) {
177
            // preserve old settings
178
            $savedDir = $this->dir;
179
            $savedPhingFile = $this->phingFile;
180
            $savedTarget = $this->newTarget;
181
182
            // set no specific target for files in filesets
183
            // [HL] I'm commenting this out; I don't know why this should not be supported!
184
            // $this->newTarget = null;
185
186
            foreach ($this->filesets as $fs) {
187
                $ds = $fs->getDirectoryScanner($this->project);
188
189
                $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...
190
                $srcFiles = $ds->getIncludedFiles();
191
192
                foreach ($srcFiles as $fname) {
193
                    $f = new PhingFile($ds->getbasedir(), $fname);
194
                    $f = $f->getAbsoluteFile();
195
                    $this->phingFile = $f->getAbsolutePath();
196
                    $this->dir = $f->getParentFile();
197
                    $this->processFile(); // run Phing!
198
                }
199
            }
200
201
            // side effect free programming ;-)
202
            $this->dir = $savedDir;
203
            $this->phingFile = $savedPhingFile;
204
            $this->newTarget = $savedTarget;
205
206
            // [HL] change back to correct dir
207
            if ($this->dir !== null) {
208
                chdir($this->dir->getAbsolutePath());
209
            }
210
        }
211
212
        // Remove any dangling references to help the GC
213 8
        foreach ($this->properties as $property) {
214 7
            $property->setFallback(null);
215
        }
216 8
    }
217
218
    /**
219
     * Execute phing file.
220
     *
221
     * @throws BuildException
222
     * @return void
223
     */
224 11
    private function processFile()
225
    {
226 11
        $buildFailed = false;
227 11
        $savedDir = $this->dir;
228 11
        $savedPhingFile = $this->phingFile;
229 11
        $savedTarget = $this->newTarget;
230
231 11
        $savedBasedirAbsPath = null; // this is used to save the basedir *if* we change it
232
233
        try {
234 11
            $this->getNewProject();
235
236 11
            $this->initializeProject();
237
238 11
            if ($this->dir !== null) {
239 1
                $dirAbsPath = $this->dir->getAbsolutePath();
240
241
                // BE CAREFUL! -- when the basedir is changed for a project,
242
                // all calls to getAbsolutePath() on a relative-path dir will
243
                // be made relative to the project's basedir!  This means
244
                // that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!
245
246
                // We need to save the current project's basedir first.
247 1
                $savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();
248
249 1
                $this->newProject->setBasedir($this->dir);
250
251
                // Now we must reset $this->dir so that it continues to resolve to the same
252
                // path.
253 1
                $this->dir = new PhingFile($dirAbsPath);
254
255 1
                if ($savedDir !== null) { // has been set explicitly
256 1
                    $this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());
257
                }
258
            } else {
259
                // Since we're not changing the basedir here (for file resolution),
260
                // we don't need to worry about any side-effects in this scanrio.
261 10
                $this->dir = $this->getProject()->getBasedir();
262
            }
263
264 11
            $this->overrideProperties();
265 11
            if ($this->phingFile === null) {
266
                $this->phingFile = "build.xml";
267
            }
268
269 11
            $fu = new FileUtils();
270 11
            $file = $fu->resolveFile($this->dir, $this->phingFile);
271 11
            $this->phingFile = $file->getAbsolutePath();
272
273 11
            $this->log(
274 11
                "Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'",
275 11
                Project::MSG_VERBOSE
276
            );
277
278 11
            $this->newProject->setUserProperty("phing.file", $this->phingFile);
279
280 11
            ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));
281
282 11
            if ($this->newTarget === null) {
283
                $this->newTarget = $this->newProject->getDefaultTarget();
284
            }
285
286
            // Are we trying to call the target in which we are defined?
287
            if (
288 11
                $this->newProject->getBaseDir() == $this->project->getBaseDir()
289 11
                && $this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file")
290 11
                && $this->getOwningTarget() !== null
291 11
                && $this->newTarget == $this->getOwningTarget()->getName()
292
            ) {
293 2
                throw new BuildException("phing task calling its own parent target");
294
            }
295
296 10
            $this->addReferences();
297 10
            $this->newProject->executeTarget($this->newTarget);
298 3
        } catch (Exception $e) {
299 3
            $buildFailed = true;
300 3
            $this->log($e->getMessage(), Project::MSG_ERR);
301 3
            if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {
302 3
                $lines = explode("\n", $e->getTraceAsString());
303 3
                foreach ($lines as $line) {
304 3
                    $this->log($line, Project::MSG_DEBUG);
305
                }
306
            }
307
            // important!!! continue on to perform cleanup tasks.
308
        }
309
310
        // reset environment values to prevent side-effects.
311
312 11
        $this->newProject = null;
313 11
        $pkeys = array_keys($this->properties);
314 11
        foreach ($pkeys as $k) {
315 7
            $this->properties[$k]->setProject(null);
316
        }
317
318 11
        $this->dir = $savedDir;
319 11
        $this->phingFile = $savedPhingFile;
320 11
        $this->newTarget = $savedTarget;
321
322
        // If the basedir for any project was changed, we need to set that back here.
323 11
        if ($savedBasedirAbsPath !== null) {
324 1
            chdir($savedBasedirAbsPath);
325
        }
326
327 11
        if ($this->haltOnFailure && $buildFailed) {
328 3
            throw new BuildException("Execution of the target buildfile failed. Aborting.");
329
        }
330 8
    }
331
332
    /**
333
     * Get the (sub)-Project instance currently in use.
334
     *
335
     * @return Project
336
     */
337 11
    protected function getNewProject(): \Project
338
    {
339 11
        if ($this->newProject === null) {
340 6
            $this->reinit();
341
        }
342 11
        return $this->newProject;
343
    }
344
345
    /**
346
     * Configure the Project, i.e. make intance, attach build listeners
347
     * (copy from father project), add Task and Datatype definitions,
348
     * copy properties and references from old project if these options
349
     * are set via the attributes of the XML tag.
350
     *
351
     * Developer note:
352
     * This function replaces the old methods "init", "_reinit" and
353
     * "_initializeProject".
354
     */
355 11
    private function initializeProject()
356
    {
357 11
        $this->newProject->setInputHandler($this->project->getInputHandler());
358
359 11
        foreach ($this->project->getBuildListeners() as $listener) {
360 11
            $this->newProject->addBuildListener($listener);
361
        }
362
363
        /* Copy things from old project. Datatypes and Tasks are always
364
         * copied, properties and references only if specified so/not
365
         * specified otherwise in the XML definition.
366
         */
367
        // Add Datatype definitions
368 11
        foreach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {
369 11
            $this->newProject->addDataTypeDefinition($typeName, $typeClass);
370
        }
371
372
        // Add Task definitions
373 11
        foreach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {
374 11
            if ($taskClass == "propertytask") {
375
                // we have already added this taskdef in init()
376
                continue;
377
            }
378 11
            $this->newProject->addTaskDefinition($taskName, $taskClass);
379
        }
380
381
        // set user-defined properties
382 11
        $this->project->copyUserProperties($this->newProject);
383
384 11
        if (!$this->inheritAll) {
385
            // set System built-in properties separately,
386
            // b/c we won't inherit them.
387 7
            $this->newProject->setSystemProperties();
388
        } else {
389
            // set all properties from calling project
390 4
            $properties = $this->project->getProperties();
391 4
            foreach ($properties as $name => $value) {
392 4
                if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {
393
                    // basedir and phing.file get special treatment in main()
394 4
                    continue;
395
                }
396
                // don't re-set user properties, avoid the warning message
397 4
                if ($this->newProject->getProperty($name) === null) {
398
                    // no user property
399 4
                    $this->newProject->setNewProperty($name, $value);
400
                }
401
            }
402
        }
403 11
    }
404
405
    /**
406
     * Override the properties in the new project with the one
407
     * explicitly defined as nested elements here.
408
     *
409
     * @return void
410
     * @throws BuildException
411
     */
412 11
    private function overrideProperties()
413
    {
414 11
        foreach (array_keys($this->properties) as $i) {
415 7
            $p = $this->properties[$i];
416 7
            $p->setProject($this->newProject);
417 7
            $p->main();
418
        }
419 11
        $this->project->copyInheritedProperties($this->newProject);
420 11
    }
421
422
    /**
423
     * Add the references explicitly defined as nested elements to the
424
     * new project.  Also copy over all references that don't override
425
     * existing references in the new project if inheritrefs has been
426
     * requested.
427
     *
428
     * @return void
429
     * @throws BuildException
430
     */
431 10
    private function addReferences()
432
    {
433
434
        // parent project references
435 10
        $projReferences = $this->project->getReferences();
436
437 10
        $newReferences = $this->newProject->getReferences();
438
439 10
        $subprojRefKeys = [];
440
441 10
        if (count($this->references) > 0) {
442
            for ($i = 0, $count = count($this->references); $i < $count; $i++) {
443
                $ref = $this->references[$i];
444
                $refid = $ref->getRefId();
445
446
                if ($refid === null) {
447
                    throw new BuildException(
448
                        "the refid attribute is required"
449
                        . " for reference elements"
450
                    );
451
                }
452
                if (!isset($projReferences[$refid])) {
453
                    $this->log(
454
                        "Parent project doesn't contain any reference '"
455
                        . $refid . "'",
456
                        Project::MSG_WARN
457
                    );
458
                    continue;
459
                }
460
461
                $subprojRefKeys[] = $refid;
462
                //thisReferences.remove(refid);
463
                $toRefid = $ref->getToRefid();
464
                if ($toRefid === null) {
465
                    $toRefid = $refid;
466
                }
467
                $this->copyReference($refid, $toRefid);
468
            }
469
        }
470
471
        // Now add all references that are not defined in the
472
        // subproject, if inheritRefs is true
473 10
        if ($this->inheritRefs) {
474
            // get the keys that are were not used by the subproject
475
            $unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);
476
477
            foreach ($unusedRefKeys as $key) {
478
                if (isset($newReferences[$key])) {
479
                    continue;
480
                }
481
                $this->copyReference($key, $key);
482
            }
483
        }
484 10
    }
485
486
    /**
487
     * Try to clone and reconfigure the object referenced by oldkey in
488
     * the parent project and add it to the new project with the key
489
     * newkey.
490
     *
491
     * <p>If we cannot clone it, copy the referenced object itself and
492
     * keep our fingers crossed.</p>
493
     *
494
     * @param  string $oldKey
495
     * @param  string $newKey
496
     * @throws BuildException
497
     * @return void
498
     */
499
    private function copyReference($oldKey, $newKey)
500
    {
501
        $orig = $this->project->getReference($oldKey);
502
        if ($orig === null) {
503
            $this->log(
504
                "No object referenced by " . $oldKey . ". Can't copy to "
505
                . $newKey,
506
                Project::MSG_WARN
507
            );
508
509
            return;
510
        }
511
512
        $copy = clone $orig;
513
514
        if ($copy instanceof ProjectComponent) {
515
            $copy->setProject($this->newProject);
516
        } elseif (in_array('setProject', get_class_methods(get_class($copy)))) {
517
            $copy->setProject($this->newProject);
518
        } elseif (!($copy instanceof Project)) {
519
            // don't copy the old "Project" itself
520
            $msg = "Error setting new project instance for "
521
                . "reference with id " . $oldKey;
522
            throw new BuildException($msg);
523
        }
524
525
        $this->newProject->addReference($newKey, $copy);
526
    }
527
528
    /**
529
     * If true, pass all properties to the new phing project.
530
     * Defaults to true.
531
     *
532
     * @param $value
533
     */
534 10
    public function setInheritAll($value)
535
    {
536 10
        $this->inheritAll = (bool) $value;
537 10
    }
538
539
    /**
540
     * If true, pass all references to the new phing project.
541
     * Defaults to false.
542
     *
543
     * @param $value
544
     */
545 10
    public function setInheritRefs($value)
546
    {
547 10
        $this->inheritRefs = (bool) $value;
548 10
    }
549
550
    /**
551
     * The directory to use as a base directory for the new phing project.
552
     * Defaults to the current project's basedir, unless inheritall
553
     * has been set to false, in which case it doesn't have a default
554
     * value. This will override the basedir setting of the called project.
555
     *
556
     * @param $d
557
     */
558 1
    public function setDir($d)
559
    {
560 1
        if (is_string($d)) {
561 1
            $this->dir = new PhingFile($d);
562
        } else {
563
            $this->dir = $d;
564
        }
565 1
    }
566
567
    /**
568
     * The build file to use.
569
     * Defaults to "build.xml". This file is expected to be a filename relative
570
     * to the dir attribute given.
571
     *
572
     * @param $s
573
     */
574 11
    public function setPhingFile($s)
575
    {
576
        // it is a string and not a file to handle relative/absolute
577
        // otherwise a relative file will be resolved based on the current
578
        // basedir.
579 11
        $this->phingFile = $s;
580 11
    }
581
582
    /**
583
     * Alias function for setPhingfile
584
     *
585
     * @param $s
586
     */
587
    public function setBuildfile($s)
588
    {
589
        $this->setPhingFile($s);
590
    }
591
592
    /**
593
     * The target of the new Phing project to execute.
594
     * Defaults to the new project's default target.
595
     *
596
     * @param string $s
597
     */
598 13
    public function setTarget(string $s)
599
    {
600 13
        if ('' === $s) {
601 1
            throw new BuildException("target attribute must not be empty");
602
        }
603
604 12
        $this->newTarget = $s;
605 12
    }
606
607
    /**
608
     * Property to pass to the new project.
609
     * The property is passed as a 'user property'
610
     */
611 7
    public function createProperty()
612
    {
613 7
        $p = new PropertyTask();
614 7
        $p->setFallback($this->getNewProject());
615 7
        $p->setUserProperty(true);
616 7
        $this->properties[] = $p;
617
618 7
        return $p;
619
    }
620
621
    /**
622
     * Reference element identifying a data type to carry
623
     * over to the new project.
624
     *
625
     * @param PhingReference $ref
626
     */
627 1
    public function addReference(PhingReference $ref)
628
    {
629 1
        $this->references[] = $ref;
630 1
    }
631
}
632