Passed
Push — main ( 4889b2...808ce0 )
by Michiel
07:43
created

ProjectConfigurator::_parse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 19
ccs 12
cts 12
cp 1
crap 1
rs 9.9
c 0
b 0
f 0
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
namespace Phing\Parser;
21
22
use Exception;
23
use Phing\Exception\BuildException;
24
use Phing\Exception\ExitStatusException;
25
use Phing\Exception\NullPointerException;
26
use Phing\ExtensionPoint;
27
use Phing\IntrospectionHelper;
28
use Phing\Io\BufferedReader;
29
use Phing\Io\File;
30
use Phing\Io\FileReader;
31
use Phing\Io\IOException;
32
use Phing\Project;
33
use Phing\Target;
34
use Phing\Task;
35
use Phing\TypeAdapter;
36
use Phing\UnknownElement;
37
38
/**
39
 * The datatype handler class.
40
 *
41
 * This class handles the occurrence of registered datatype tags like
42
 * FileSet
43
 *
44
 * @author    Andreas Aderhold <[email protected]>
45
 * @copyright 2001,2002 THYRELL. All rights reserved
46
 */
47
class ProjectConfigurator
48
{
49
    public const PARSING_CONTEXT_REFERENCE = "phing.parsing.context";
50
51
    /**
52
     * @var Project $project
53
     */
54
    public $project;
55
    public $locator;
56
57
    public $buildFile;
58
    public $buildFileParent;
59
60
    /**
61
     * Synthetic target that will be called at the end to the parse phase
62
     */
63
    private $parseEndTarget;
64
65
    /**
66
     * Name of the current project
67
     */
68
    private $currentProjectName;
69
70
    private $isParsing = true;
71
72
    /**
73
     * Indicates whether the project tag attributes are to be ignored
74
     * when processing a particular build file.
75
     */
76
    private $ignoreProjectTag = false;
77
78
    /**
79
     * Static call to ProjectConfigurator. Use this to configure a
80
     * project. Do not use the new operator.
81
     *
82
     * @param Project $project the Project instance this configurator should use
83
     * @param File $buildFile the buildfile object the parser should use
84
     *
85
     * @throws IOException
86
     * @throws BuildException
87
     * @throws NullPointerException
88
     */
89 846
    public static function configureProject(Project $project, File $buildFile): void
90
    {
91 846
        (new self($project, $buildFile))->parse();
92 846
    }
93
94
    /**
95
     * Constructs a new ProjectConfigurator object
96
     * This constructor is private. Use a static call to
97
     * <code>configureProject</code> to configure a project.
98
     *
99
     * @param Project $project the Project instance this configurator should use
100
     * @param File $buildFile the buildfile object the parser should use
101
     * @throws IOException
102
     * @throws NullPointerException
103
     */
104 846
    private function __construct(Project $project, File $buildFile)
105
    {
106 846
        $this->project = $project;
107 846
        $this->buildFile = new File($buildFile->getAbsolutePath());
108 846
        $this->buildFileParent = new File($this->buildFile->getParent());
109 846
        $this->parseEndTarget = new Target();
110 846
    }
111
112
    /**
113
     * find out the build file
114
     *
115
     * @return File the build file to which the xml context belongs
116
     */
117 846
    public function getBuildFile()
118
    {
119 846
        return $this->buildFile;
120
    }
121
122
    /**
123
     * find out the parent build file of this build file
124
     *
125
     * @return File the parent build file of this build file
126
     */
127
    public function getBuildFileParent()
128
    {
129
        return $this->buildFileParent;
130
    }
131
132
    /**
133
     * find out the current project name
134
     *
135
     * @return string current project name
136
     */
137 92
    public function getCurrentProjectName()
138
    {
139 92
        return $this->currentProjectName;
140
    }
141
142
    /**
143
     * set the name of the current project
144
     *
145
     * @param string $name name of the current project
146
     */
147 846
    public function setCurrentProjectName($name)
148
    {
149 846
        $this->currentProjectName = $name;
150 846
    }
151
152
    /**
153
     * tells whether the project tag is being ignored
154
     *
155
     * @return bool whether the project tag is being ignored
156
     */
157 846
    public function isIgnoringProjectTag()
158
    {
159 846
        return $this->ignoreProjectTag;
160
    }
161
162
    /**
163
     * sets the flag to ignore the project tag
164
     *
165
     * @param bool $flag flag to ignore the project tag
166
     */
167 100
    public function setIgnoreProjectTag($flag)
168
    {
169 100
        $this->ignoreProjectTag = $flag;
170 100
    }
171
172
    /**
173
     * @return bool
174
     */
175
    public function isParsing()
176
    {
177
        return $this->isParsing;
178
    }
179
180
    /**
181
     * Creates the ExpatParser, sets root handler and kick off parsing
182
     * process.
183
     *
184
     * @throws BuildException if there is any kind of exception during
185
     *                        the parsing process
186
     */
187 846
    protected function parse()
188
    {
189
        try {
190
            // get parse context
191 846
            $ctx = $this->project->getReference(self::PARSING_CONTEXT_REFERENCE);
192 846
            if (null == $ctx) {
193
                // make a new context and register it with project
194 846
                $ctx = new XmlContext($this->project);
195 846
                $this->project->addReference(self::PARSING_CONTEXT_REFERENCE, $ctx);
196
            }
197
198
            //record this parse with context
199 846
            $ctx->addImport($this->buildFile);
200
201 846
            if (count($ctx->getImportStack()) > 1) {
202 100
                $currentImplicit = $ctx->getImplicitTarget();
203 100
                $currentTargets = $ctx->getCurrentTargets();
204
205 100
                $newCurrent = new Target();
206 100
                $newCurrent->setProject($this->project);
207 100
                $newCurrent->setName('');
208 100
                $ctx->setCurrentTargets([]);
209 100
                $ctx->setImplicitTarget($newCurrent);
210
211
                // this is an imported file
212
                // modify project tag parse behavior
213 100
                $this->setIgnoreProjectTag(true);
214 100
                $this->innerParse($ctx);
215 100
                $newCurrent->main();
216
217 100
                $ctx->setImplicitTarget($currentImplicit);
218 100
                $ctx->setCurrentTargets($currentTargets);
219
            } else {
220 846
                $ctx->setCurrentTargets([]);
221 846
                $this->innerParse($ctx);
222 846
                $ctx->getImplicitTarget()->main();
223
            }
224
225 846
            $this->resolveExtensionOfAttributes($this->project, $ctx);
226 3
        } catch (Exception $exc) {
227
            //throw new BuildException("Error reading project file", $exc);
228 3
            throw $exc;
229
        }
230 846
    }
231
232
    /**
233
     * @param XmlContext $ctx
234
     * @throws ExpatParseException
235
     */
236 846
    protected function innerParse(XmlContext $ctx)
237
    {
238
        // push action onto global stack
239 846
        $ctx->startConfigure($this);
240
241 846
        $reader = new BufferedReader(new FileReader($this->buildFile));
242 846
        $parser = new ExpatParser($reader);
243 846
        $parser->parserSetOption(XML_OPTION_CASE_FOLDING, 0);
244 846
        $parser->setHandler(new RootHandler($parser, $this, $ctx));
245 846
        $this->project->log("parsing buildfile " . $this->buildFile->getName(), Project::MSG_VERBOSE);
246 846
        $parser->parse();
247 846
        $reader->close();
248
249
        // mark parse phase as completed
250 846
        $this->isParsing = false;
251
        // execute delayed tasks
252 846
        $this->parseEndTarget->main();
253
        // pop this action from the global stack
254 846
        $ctx->endConfigure();
255 846
    }
256
257
    /**
258
     * Delay execution of a task until after the current parse phase has
259
     * completed.
260
     *
261
     * @param Task $task Task to execute after parse
262
     */
263
    public function delayTaskUntilParseEnd($task)
264
    {
265
        $this->parseEndTarget->addTask($task);
266
    }
267
268
    /**
269
     * Configures an element and resolves eventually given properties.
270
     *
271
     * @param mixed $target element to configure
272
     * @param array $attrs element's attributes
273
     * @param Project $project project this element belongs to
274
     * @throws BuildException
275
     * @throws Exception
276
     */
277 702
    public static function configure($target, $attrs, Project $project)
278
    {
279 702
        if ($target instanceof TypeAdapter) {
280 232
            $target = $target->getProxy();
281
        }
282
283
        // if the target is an UnknownElement, this means that the tag had not been registered
284
        // when the enclosing element (task, target, etc.) was configured.  It is possible, however,
285
        // that the tag was registered (e.g. using <taskdef>) after the original configuration.
286
        // ... so, try to load it again:
287 702
        if ($target instanceof UnknownElement) {
288
            $tryTarget = $project->createTask($target->getTaskType());
289
            if ($tryTarget) {
0 ignored issues
show
introduced by
$tryTarget is of type Phing\Task, thus it always evaluated to true.
Loading history...
290
                $target = $tryTarget;
291
            }
292
        }
293
294 702
        $bean = get_class($target);
295 702
        $ih = IntrospectionHelper::getHelper($bean);
296
297 702
        foreach ($attrs as $key => $value) {
298 692
            if ($key === 'id') {
299 79
                continue;
300
                // throw new BuildException("Id must be set Extermnally");
301
            }
302 692
            if (!is_string($value) && method_exists($value, 'main')) {
303
                $value = $value->main();
304
            } else {
305 692
                $value = $project->replaceProperties($value);
306
            }
307
            try { // try to set the attribute
308 692
                $ih->setAttribute($project, $target, strtolower($key), $value);
309 15
            } catch (BuildException $be) {
310
                // id attribute must be set externally
311 15
                if ($key !== 'id') {
312 15
                    throw $be;
313
                }
314
            }
315
        }
316 702
    }
317
318
    /**
319
     * Configures the #CDATA of an element.
320
     *
321
     * @param Project $project the project this element belongs to
322
     * @param object  the element to configure
0 ignored issues
show
Bug introduced by
The type Phing\Parser\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
323
     * @param string $text the element's #CDATA
324
     */
325 482
    public static function addText($project, $target, $text = null)
326
    {
327 482
        if ($text === null || strlen(trim($text)) === 0) {
328 443
            return;
329
        }
330 68
        $ih = IntrospectionHelper::getHelper(get_class($target));
331 68
        $text = $project->replaceProperties($text);
332 68
        $ih->addText($project, $target, $text);
333 68
    }
334
335
    /**
336
     * Stores a configured child element into its parent object
337
     *
338
     * @param object  the project this element belongs to
339
     * @param object  the parent element
340
     * @param object  the child element
341
     * @param string  the XML tagname
342
     */
343
    public static function storeChild($project, $parent, $child, $tag)
344
    {
345
        $ih = IntrospectionHelper::getHelper(get_class($parent));
346
        $ih->storeElement($project, $parent, $child, $tag);
347
    }
348
349
    /**
350
     * Scan Attributes for the id attribute and maybe add a reference to
351
     * project.
352
     *
353
     * @param object $target the element's object
354
     * @param array $attr the element's attributes
355
     */
356 841
    public function configureId($target, $attr)
357
    {
358 841
        if (isset($attr['id']) && $attr['id'] !== null) {
359 108
            $this->project->addReference($attr['id'], $target);
360
        }
361 841
    }
362
363
    /**
364
     * Add location to build exception.
365
     *
366
     * @param BuildException $ex the build exception, if the build exception
367
     *                                    does not include
368
     * @param Location $newLocation the location of the calling task (may be null)
369
     * @return BuildException a new build exception based in the build exception with
370
     *         location set to newLocation. If the original exception
371
     *         did not have a location, just return the build exception
372
     */
373 2
    public static function addLocationToBuildException(BuildException $ex, Location $newLocation)
374
    {
375 2
        if ($ex->getLocation() === null || $ex->getMessage() === null) {
376
            return $ex;
377
        }
378 2
        $errorMessage = sprintf(
379 2
            "The following error occurred while executing this line:%s%s %s%s",
380 2
            PHP_EOL,
381 2
            $ex->getLocation(),
382 2
            $ex->getMessage(),
383 2
            PHP_EOL
384
        );
385 2
        if ($ex instanceof ExitStatusException) {
386
            $exitStatus = $ex->getCode();
387
            if ($newLocation === null) {
388
                return new ExitStatusException($errorMessage, $exitStatus);
389
            }
390
            return new ExitStatusException($errorMessage, $exitStatus, $newLocation);
391
        }
392
393 2
        return new BuildException($errorMessage, $ex, $newLocation);
394
    }
395
396
    /**
397
     * Check extensionStack and inject all targets having extensionOf attributes
398
     * into extensionPoint.
399
     * <p>
400
     * This method allow you to defer injection and have a powerful control of
401
     * extensionPoint wiring.
402
     * </p>
403
     * <p>
404
     * This should be invoked by each concrete implementation of ProjectHelper
405
     * when the root "buildfile" and all imported/included buildfile are loaded.
406
     * </p>
407
     *
408
     * @param Project $project The project containing the target. Must not be
409
     *            <code>null</code>.
410
     * @throws BuildException if OnMissingExtensionPoint.FAIL and
411
     *                extensionPoint does not exist
412
     * @see OnMissingExtensionPoint
413
     */
414 846
    public function resolveExtensionOfAttributes(Project $project, XmlContext $ctx)
415
    {
416
        /** @var XmlContext $ctx */
417 846
        foreach ($ctx->getExtensionPointStack() as [$extPointName, $targetName, $missingBehaviour, $prefixAndSep]) {
418
            // find the target we're extending
419 8
            $projectTargets = $project->getTargets();
420 8
            $extPoint = null;
421 8
            if ($prefixAndSep === null) {
422
                // no prefix - not from an imported/included build file
423 8
                $extPoint = $projectTargets[$extPointName] ?? null;
424
            } else {
425
                // we have a prefix, which means we came from an include/import
426
427
                // FIXME: here we handle no particular level of include. We try
428
                // the fully prefixed name, and then the non-prefixed name. But
429
                // there might be intermediate project in the import stack,
430
                // which prefix should be tested before testing the non-prefix
431
                // root name.
432
433
                $extPoint = $projectTargets[$prefixAndSep . $extPointName] ?? null;
434
                if ($extPoint === null) {
435
                    $extPoint = $projectTargets[$extPointName] ?? null;
436
                }
437
            }
438
439
            // make sure we found a point to extend on
440 8
            if ($extPoint === null) {
441 3
                $message = "can't add target " . $targetName
442 3
                    . " to extension-point " . $extPointName
443 3
                    . " because the extension-point is unknown.";
444 3
                if ($missingBehaviour === 'fail') {
445 1
                    throw new BuildException($message);
446
                }
447 2
                if ($missingBehaviour === 'warn') {
448 1
                    $t = $projectTargets[$targetName];
0 ignored issues
show
Unused Code introduced by
The assignment to $t is dead and can be removed.
Loading history...
449 2
                    $project->log("Warning: " . $message, Project::MSG_WARN);
450
                }
451
            } else {
452 5
                if (!$extPoint instanceof ExtensionPoint) {
453 1
                    throw new BuildException("referenced target " . $extPointName
454 1
                        . " is not an extension-point");
455
                }
456 4
                $extPoint->addDependency($targetName);
457
            }
458
        }
459 846
    }
460
}
461