Passed
Push — master ( e5c614...6595be )
by Siad
13:10
created

ProjectConfigurator   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Test Coverage

Coverage 83.45%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 126
dl 0
loc 410
ccs 116
cts 139
cp 0.8345
rs 8.64
c 1
b 0
f 0
wmc 47

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isIgnoringProjectTag() 0 3 1
A getBuildFile() 0 3 1
A getBuildFileParent() 0 3 1
A setIgnoreProjectTag() 0 3 1
A getCurrentProjectName() 0 3 1
A __construct() 0 6 1
A isParsing() 0 3 1
A configureProject() 0 3 1
A setCurrentProjectName() 0 3 1
A addText() 0 8 3
A delayTaskUntilParseEnd() 0 3 1
B resolveExtensionOfAttributes() 0 43 11
B configure() 0 36 9
A storeChild() 0 4 1
A _parse() 0 19 1
A configureId() 0 4 3
A parse() 0 42 4
A addLocationToBuildException() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like ProjectConfigurator 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 ProjectConfigurator, 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
 * The datatype handler class.
22
 *
23
 * This class handles the occurrence of registered datatype tags like
24
 * FileSet
25
 *
26
 * @author    Andreas Aderhold <[email protected]>
27
 * @copyright 2001,2002 THYRELL. All rights reserved
28
 * @package   phing.parser
29
 */
30
class ProjectConfigurator
31
{
32
    public const PARSING_CONTEXT_REFERENCE = "phing.parsing.context";
33
34
    /**
35
     * @var Project $project
36
     */
37
    public $project;
38
    public $locator;
39
40
    public $buildFile;
41
    public $buildFileParent;
42
43
    /**
44
     * Synthetic target that will be called at the end to the parse phase
45
     */
46
    private $parseEndTarget;
47
48
    /**
49
     * Name of the current project
50
     */
51
    private $currentProjectName;
52
53
    private $isParsing = true;
54
55
    /**
56
     * Indicates whether the project tag attributes are to be ignored
57
     * when processing a particular build file.
58
     */
59
    private $ignoreProjectTag = false;
60
61
    /**
62
     * Static call to ProjectConfigurator. Use this to configure a
63
     * project. Do not use the new operator.
64
     *
65
     * @param Project $project the Project instance this configurator should use
66
     * @param PhingFile $buildFile the buildfile object the parser should use
67
     *
68
     * @throws \IOException
69
     * @throws \BuildException
70
     * @throws NullPointerException
71
     */
72 792
    public static function configureProject(Project $project, PhingFile $buildFile): void
73
    {
74 792
        (new self($project, $buildFile))->parse();
75 792
    }
76
77
    /**
78
     * Constructs a new ProjectConfigurator object
79
     * This constructor is private. Use a static call to
80
     * <code>configureProject</code> to configure a project.
81
     *
82
     * @param  Project $project the Project instance this configurator should use
83
     * @param  PhingFile $buildFile the buildfile object the parser should use
84
     * @throws IOException
85
     * @throws NullPointerException
86
     */
87 792
    private function __construct(Project $project, PhingFile $buildFile)
88
    {
89 792
        $this->project = $project;
90 792
        $this->buildFile = new PhingFile($buildFile->getAbsolutePath());
91 792
        $this->buildFileParent = new PhingFile($this->buildFile->getParent());
92 792
        $this->parseEndTarget = new Target();
93 792
    }
94
95
    /**
96
     * find out the build file
97
     *
98
     * @return PhingFile the build file to which the xml context belongs
99
     */
100 792
    public function getBuildFile()
101
    {
102 792
        return $this->buildFile;
103
    }
104
105
    /**
106
     * find out the parent build file of this build file
107
     *
108
     * @return PhingFile the parent build file of this build file
109
     */
110
    public function getBuildFileParent()
111
    {
112
        return $this->buildFileParent;
113
    }
114
115
    /**
116
     * find out the current project name
117
     *
118
     * @return string current project name
119
     */
120 92
    public function getCurrentProjectName()
121
    {
122 92
        return $this->currentProjectName;
123
    }
124
125
    /**
126
     * set the name of the current project
127
     *
128
     * @param string $name name of the current project
129
     */
130 792
    public function setCurrentProjectName($name)
131
    {
132 792
        $this->currentProjectName = $name;
133 792
    }
134
135
    /**
136
     * tells whether the project tag is being ignored
137
     *
138
     * @return bool whether the project tag is being ignored
139
     */
140 792
    public function isIgnoringProjectTag()
141
    {
142 792
        return $this->ignoreProjectTag;
143
    }
144
145
    /**
146
     * sets the flag to ignore the project tag
147
     *
148
     * @param bool $flag flag to ignore the project tag
149
     */
150 100
    public function setIgnoreProjectTag($flag)
151
    {
152 100
        $this->ignoreProjectTag = $flag;
153 100
    }
154
155
    /**
156
     * @return bool
157
     */
158
    public function isParsing()
159
    {
160
        return $this->isParsing;
161
    }
162
163
    /**
164
     * Creates the ExpatParser, sets root handler and kick off parsing
165
     * process.
166
     *
167
     * @throws BuildException if there is any kind of exception during
168
     *                        the parsing process
169
     */
170 792
    protected function parse()
171
    {
172
        try {
173
            // get parse context
174 792
            $ctx = $this->project->getReference(self::PARSING_CONTEXT_REFERENCE);
175 792
            if (null == $ctx) {
176
                // make a new context and register it with project
177 792
                $ctx = new PhingXMLContext($this->project);
178 792
                $this->project->addReference(self::PARSING_CONTEXT_REFERENCE, $ctx);
179
            }
180
181
            //record this parse with context
182 792
            $ctx->addImport($this->buildFile);
183
184 792
            if (count($ctx->getImportStack()) > 1) {
185 100
                $currentImplicit = $ctx->getImplicitTarget();
186 100
                $currentTargets = $ctx->getCurrentTargets();
187
188 100
                $newCurrent = new Target();
189 100
                $newCurrent->setProject($this->project);
190 100
                $newCurrent->setName('');
191 100
                $ctx->setCurrentTargets([]);
192 100
                $ctx->setImplicitTarget($newCurrent);
193
194
                // this is an imported file
195
                // modify project tag parse behavior
196 100
                $this->setIgnoreProjectTag(true);
197 100
                $this->_parse($ctx);
198 100
                $newCurrent->main();
199
200 100
                $ctx->setImplicitTarget($currentImplicit);
201 100
                $ctx->setCurrentTargets($currentTargets);
202
            } else {
203 792
                $ctx->setCurrentTargets([]);
204 792
                $this->_parse($ctx);
205 792
                $ctx->getImplicitTarget()->main();
206
            }
207
208 792
            $this->resolveExtensionOfAttributes($this->project, $ctx);
209 4
        } catch (Exception $exc) {
210
            //throw new BuildException("Error reading project file", $exc);
211 4
            throw $exc;
212
        }
213 792
    }
214
215
    /**
216
     * @param PhingXMLContext $ctx
217
     * @throws ExpatParseException
218
     */
219 792
    protected function _parse(PhingXMLContext $ctx)
220
    {
221
        // push action onto global stack
222 792
        $ctx->startConfigure($this);
223
224 792
        $reader = new BufferedReader(new FileReader($this->buildFile));
225 792
        $parser = new ExpatParser($reader);
226 792
        $parser->parserSetOption(XML_OPTION_CASE_FOLDING, 0);
227 792
        $parser->setHandler(new RootHandler($parser, $this, $ctx));
228 792
        $this->project->log("parsing buildfile " . $this->buildFile->getName(), Project::MSG_VERBOSE);
229 792
        $parser->parse();
230 792
        $reader->close();
231
232
        // mark parse phase as completed
233 792
        $this->isParsing = false;
234
        // execute delayed tasks
235 792
        $this->parseEndTarget->main();
236
        // pop this action from the global stack
237 792
        $ctx->endConfigure();
238 792
    }
239
240
    /**
241
     * Delay execution of a task until after the current parse phase has
242
     * completed.
243
     *
244
     * @param Task $task Task to execute after parse
245
     */
246
    public function delayTaskUntilParseEnd($task)
247
    {
248
        $this->parseEndTarget->addTask($task);
249
    }
250
251
    /**
252
     * Configures an element and resolves eventually given properties.
253
     *
254
     * @param  mixed $target element to configure
255
     * @param  array $attrs element's attributes
256
     * @param  Project $project project this element belongs to
257
     * @throws BuildException
258
     * @throws Exception
259
     */
260 644
    public static function configure($target, $attrs, Project $project)
261
    {
262 644
        if ($target instanceof TypeAdapter) {
263 231
            $target = $target->getProxy();
264
        }
265
266
        // if the target is an UnknownElement, this means that the tag had not been registered
267
        // when the enclosing element (task, target, etc.) was configured.  It is possible, however,
268
        // that the tag was registered (e.g. using <taskdef>) after the original configuration.
269
        // ... so, try to load it again:
270 644
        if ($target instanceof UnknownElement) {
271
            $tryTarget = $project->createTask($target->getTaskType());
272
            if ($tryTarget) {
0 ignored issues
show
introduced by
$tryTarget is of type Task, thus it always evaluated to true.
Loading history...
273
                $target = $tryTarget;
274
            }
275
        }
276
277 644
        $bean = get_class($target);
278 644
        $ih = IntrospectionHelper::getHelper($bean);
279
280 644
        foreach ($attrs as $key => $value) {
281 634
            if ($key === 'id') {
282 65
                continue;
283
                // throw new BuildException("Id must be set Extermnally");
284
            }
285 634
            if (method_exists($value, 'main')) {
286
                $value = $value->main();
287
            } else {
288 634
                $value = $project->replaceProperties($value);
289
            }
290
            try { // try to set the attribute
291 634
                $ih->setAttribute($project, $target, strtolower($key), $value);
292 8
            } catch (BuildException $be) {
293
                // id attribute must be set externally
294 8
                if ($key !== 'id') {
295 8
                    throw $be;
296
                }
297
            }
298
        }
299 643
    }
300
301
    /**
302
     * Configures the #CDATA of an element.
303
     *
304
     * @param Project $project the project this element belongs to
305
     * @param object  the element to configure
306
     * @param string $text the element's #CDATA
307
     */
308 443
    public static function addText($project, $target, $text = null)
309
    {
310 443
        if ($text === null || strlen(trim($text)) === 0) {
311 406
            return;
312
        }
313 65
        $ih = IntrospectionHelper::getHelper(get_class($target));
314 65
        $text = $project->replaceProperties($text);
315 65
        $ih->addText($project, $target, $text);
316 65
    }
317
318
    /**
319
     * Stores a configured child element into its parent object
320
     *
321
     * @param object  the project this element belongs to
322
     * @param object  the parent element
323
     * @param object  the child element
324
     * @param string  the XML tagname
325
     */
326
    public static function storeChild($project, $parent, $child, $tag)
327
    {
328
        $ih = IntrospectionHelper::getHelper(get_class($parent));
329
        $ih->storeElement($project, $parent, $child, $tag);
330
    }
331
332
    /**
333
     * Scan Attributes for the id attribute and maybe add a reference to
334
     * project.
335
     *
336
     * @param object $target the element's object
337
     * @param array $attr the element's attributes
338
     */
339 787
    public function configureId($target, $attr)
340
    {
341 787
        if (isset($attr['id']) && $attr['id'] !== null) {
342 80
            $this->project->addReference($attr['id'], $target);
343
        }
344 787
    }
345
346
    /**
347
     * Add location to build exception.
348
     *
349
     * @param  BuildException $ex the build exception, if the build exception
350
     *                                    does not include
351
     * @param  Location $newLocation the location of the calling task (may be null)
352
     * @return BuildException a new build exception based in the build exception with
353
     *         location set to newLocation. If the original exception
354
     *         did not have a location, just return the build exception
355
     */
356 2
    public static function addLocationToBuildException(BuildException $ex, Location $newLocation)
357
    {
358 2
        if ($ex->getLocation() === null || $ex->getMessage() === null) {
359
            return $ex;
360
        }
361 2
        $errorMessage = sprintf(
362 2
            "The following error occurred while executing this line:%s%s %s%s",
363 2
            PHP_EOL,
364 2
            $ex->getLocation(),
365 2
            $ex->getMessage(),
366 2
            PHP_EOL
367
        );
368 2
        if ($ex instanceof ExitStatusException) {
369
            $exitStatus = $ex->getCode();
370
            if ($newLocation === null) {
371
                return new ExitStatusException($errorMessage, $exitStatus);
372
            }
373
            return new ExitStatusException($errorMessage, $exitStatus, $newLocation);
374
        }
375
376 2
        return new BuildException($errorMessage, $ex, $newLocation);
377
    }
378
379
    /**
380
     * Check extensionStack and inject all targets having extensionOf attributes
381
     * into extensionPoint.
382
     * <p>
383
     * This method allow you to defer injection and have a powerful control of
384
     * extensionPoint wiring.
385
     * </p>
386
     * <p>
387
     * This should be invoked by each concrete implementation of ProjectHelper
388
     * when the root "buildfile" and all imported/included buildfile are loaded.
389
     * </p>
390
     *
391
     * @param Project $project The project containing the target. Must not be
392
     *            <code>null</code>.
393
     * @throws BuildException if OnMissingExtensionPoint.FAIL and
394
     *                extensionPoint does not exist
395
     * @see OnMissingExtensionPoint
396
     */
397 792
    public function resolveExtensionOfAttributes(Project $project, PhingXMLContext $ctx)
398
    {
399
        /** @var PhingXMLContext $ctx */
400 792
        foreach ($ctx->getExtensionPointStack() as [$extPointName, $targetName, $missingBehaviour, $prefixAndSep]) {
401
            // find the target we're extending
402 7
            $projectTargets = $project->getTargets();
403 7
            $extPoint = null;
404 7
            if ($prefixAndSep === null) {
405
                // no prefix - not from an imported/included build file
406 7
                $extPoint = isset($projectTargets[$extPointName]) ? $projectTargets[$extPointName] : null;
407
            } else {
408
                // we have a prefix, which means we came from an include/import
409
410
                // FIXME: here we handle no particular level of include. We try
411
                // the fully prefixed name, and then the non-prefixed name. But
412
                // there might be intermediate project in the import stack,
413
                // which prefix should be tested before testing the non-prefix
414
                // root name.
415
416
                $extPoint = isset($projectTargets[$prefixAndSep . $extPointName]) ? $projectTargets[$prefixAndSep . $extPointName] : null;
417
                if ($extPoint === null) {
418
                    $extPoint = isset($projectTargets[$extPointName]) ? $projectTargets[$extPointName] : null;
419
                }
420
            }
421
422
            // make sure we found a point to extend on
423 7
            if ($extPoint === null) {
424 3
                $message = "can't add target " . $targetName
425 3
                    . " to extension-point " . $extPointName
426 3
                    . " because the extension-point is unknown.";
427 3
                if ($missingBehaviour === 'fail') {
428 1
                    throw new BuildException($message);
429
                }
430 2
                if ($missingBehaviour === 'warn') {
431 1
                    $t = $projectTargets[$targetName];
0 ignored issues
show
Unused Code introduced by
The assignment to $t is dead and can be removed.
Loading history...
432 1
                    $project->log("Warning: " . $message, Project::MSG_WARN);
433
                }
434
            } else {
435 4
                if (!$extPoint instanceof ExtensionPoint) {
436 1
                    throw new BuildException("referenced target " . $extPointName
437 1
                        . " is not an extension-point");
438
                }
439 3
                $extPoint->addDependency($targetName);
440
            }
441
        }
442 792
    }
443
}
444