Passed
Push — master ( d68b8d...e5c614 )
by Siad
10:45
created

PropertyTask::resolveAllProperties()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 67
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 36.0794

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 38
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 67
ccs 13
cts 36
cp 0.361
crap 36.0794
rs 7.6666

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 for setting properties in buildfiles.
22
 *
23
 * @author  Andreas Aderhold <[email protected]>
24
 * @author  Hans Lellelid <[email protected]>
25
 * @package phing.tasks.system
26
 */
27
class PropertyTask extends Task
28
{
29
    use FilterChainAware;
30
31
    /**
32
     * @var string name of the property
33
     */
34
    protected $name;
35
36
    /**
37
     * @var string $value of the property
38
     */
39
    protected $value;
40
41
    /**
42
     * @var Reference
43
     */
44
    protected $reference;
45
46
    /**
47
     * @var string environment
48
     */
49
    protected $env;
50
51
    /**
52
     * @var PhingFile
53
     */
54
    protected $file;
55
56
    /**
57
     * @var string
58
     */
59
    protected $prefix;
60
61
    /**
62
     * @var Project
63
     */
64
    protected $fallback;
65
66
    /**
67
     * Whether to force overwrite of existing property.
68
     */
69
    protected $override = false;
70
71
    /**
72
     * Whether property should be treated as "user" property.
73
     */
74
    protected $userProperty = false;
75
76
    /**
77
     * Whether to log messages as INFO or VERBOSE
78
     */
79
    protected $logOutput = true;
80
81
    /**
82
     * @var FileParserFactoryInterface
83
     */
84
    private $fileParserFactory;
85
86
    /**
87
     * Whether a warning should be displayed when the property ismissing.
88
     */
89
    private $quiet = false;
90
91
    /**
92
     * Whether the task should fail when the property file is not found.
93
     */
94
    private $required = false;
95
96
    /**
97
     * @param FileParserFactoryInterface $fileParserFactory
98
     */
99 407
    public function __construct(FileParserFactoryInterface $fileParserFactory = null)
100
    {
101 407
        parent::__construct();
102 407
        $this->fileParserFactory = $fileParserFactory ?? new FileParserFactory();
103 407
    }
104
105
    /**
106
     * File required or not.
107
     *
108
     * @param string $d
109
     */
110
    public function setRequired($d)
111
    {
112
        $this->required = $d;
113
    }
114
115
    /**
116
     * @return string
117
     */
118
    public function getRequired()
119
    {
120
        return $this->required;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->required returns the type boolean which is incompatible with the documented return type string.
Loading history...
121
    }
122
123
    /**
124
     * Sets a the name of current property component
125
     *
126
     * @param string $name
127
     */
128 405
    public function setName(string $name): void
129
    {
130 405
        $this->name = $name;
131 405
    }
132
133
    /**
134
     * Get property component name.
135
     */
136 10
    public function getName()
137
    {
138 10
        return $this->name;
139
    }
140
141
    /**
142
     * Sets a the value of current property component.
143
     *
144
     * @param string $value Value of name, all scalars allowed
145
     */
146 405
    public function setValue(string $value): void
147
    {
148 405
        $this->value = $value;
149 405
    }
150
151
    /**
152
     * Sets value of property to CDATA tag contents.
153
     *
154
     * @param string $value
155
     * @since    2.2.0
156
     */
157
    public function addText(string $value): void
158
    {
159
        $this->setValue($value);
160
    }
161
162
    /**
163
     * Get the value of current property component.
164
     */
165 5
    public function getValue()
166
    {
167 5
        return $this->value;
168
    }
169
170
    /**
171
     * Set a file to use as the source for properties.
172
     *
173
     * @param $file
174
     *
175
     * @throws IOException
176
     * @throws NullPointerException
177
     */
178 11
    public function setFile($file)
179
    {
180 11
        if (is_string($file)) {
181 11
            $file = new PhingFile($file);
182
        }
183 11
        $this->file = $file;
184 11
    }
185
186
    /**
187
     * Get the PhingFile that is being used as property source.
188
     */
189 4
    public function getFile()
190
    {
191 4
        return $this->file;
192
    }
193
194
    /**
195
     * @param Reference $ref
196
     */
197
    public function setRefid(Reference $ref): void
198
    {
199
        $this->reference = $ref;
200
    }
201
202 4
    public function getRefid()
203
    {
204 4
        return $this->reference;
205
    }
206
207
    /**
208
     * Prefix to apply to properties loaded using <code>file</code>.
209
     * A "." is appended to the prefix if not specified.
210
     *
211
     * @param  string $prefix prefix string
212
     *
213
     * @since  2.0
214
     */
215
    public function setPrefix(string $prefix): void
216
    {
217
        $this->prefix = $prefix;
218
        if (!StringHelper::endsWith(".", $prefix)) {
219
            $this->prefix .= ".";
220
        }
221
    }
222
223
    /**
224
     * @return string
225
     * @since 2.0
226
     */
227 4
    public function getPrefix()
228
    {
229 4
        return $this->prefix;
230
    }
231
232
    /**
233
     * the prefix to use when retrieving environment variables.
234
     * Thus if you specify environment="myenv"
235
     * you will be able to access OS-specific
236
     * environment variables via property names "myenv.PATH" or
237
     * "myenv.TERM".
238
     * <p>
239
     * Note that if you supply a property name with a final
240
     * "." it will not be doubled. ie environment="myenv." will still
241
     * allow access of environment variables through "myenv.PATH" and
242
     * "myenv.TERM". This functionality is currently only implemented
243
     * on select platforms. Feel free to send patches to increase the number of platforms
244
     * this functionality is supported on ;).<br>
245
     * Note also that properties are case sensitive, even if the
246
     * environment variables on your operating system are not, e.g. it
247
     * will be ${env.Path} not ${env.PATH} on Windows 2000.
248
     *
249
     * @param string $env
250
     */
251 1
    public function setEnvironment(string $env): void
252
    {
253 1
        $this->env = $env;
254 1
    }
255
256 4
    public function getEnvironment()
257
    {
258 4
        return $this->env;
259
    }
260
261
    /**
262
     * Set whether this is a user property (ro).
263
     * This is deprecated in Ant 1.5, but the userProperty attribute
264
     * of the class is still being set via constructor, so Phing will
265
     * allow this method to function.
266
     *
267
     * @param boolean $v
268
     */
269 10
    public function setUserProperty(bool $v): void
270
    {
271 10
        $this->userProperty = $v;
272 10
    }
273
274
    /**
275
     * @return bool
276
     */
277 4
    public function getUserProperty()
278
    {
279 4
        return $this->userProperty;
280
    }
281
282
    /**
283
     * @param $v
284
     */
285 6
    public function setOverride(bool $v): void
286
    {
287 6
        $this->override = $v;
288 6
    }
289
290
    /**
291
     * @return bool
292
     */
293 4
    public function getOverride()
294
    {
295 4
        return $this->override;
296
    }
297
298
    /**
299
     * @return string
300
     */
301 1
    public function __toString()
302
    {
303 1
        return $this->value;
304
    }
305
306
    /**
307
     * @param Project $p
308
     */
309 10
    public function setFallback($p): void
310
    {
311 10
        $this->fallback = $p;
312 10
    }
313
314
    public function getFallback()
315
    {
316
        return $this->fallback;
317
    }
318
319
    /**
320
     * @param $logOutput
321
     */
322 4
    public function setLogoutput(bool $logOutput): void
323
    {
324 4
        $this->logOutput = $logOutput;
325 4
    }
326
327
    /**
328
     * @return bool
329
     */
330 4
    public function getLogoutput()
331
    {
332 4
        return $this->logOutput;
333
    }
334
335
    /**
336
     * Set quiet mode, which suppresses warnings if chmod() fails.
337
     *
338
     * @see   setFailonerror()
339
     * @param $bool
340
     */
341 4
    public function setQuiet(bool $bool): void
342
    {
343 4
        $this->quiet = $bool;
344 4
    }
345
346
    /**
347
     * @return bool
348
     */
349 4
    public function getQuiet(): bool
350
    {
351 4
        return $this->quiet;
352
    }
353
354
    /**
355
     * set the property in the project to the value.
356
     * if the task was give a file or env attribute
357
     * here is where it is loaded
358
     */
359 407
    public function main()
360
    {
361 407
        $this->validate();
362
363 407
        if ($this->name !== null && $this->value !== null) {
364 405
            $this->addProperty($this->name, $this->value);
365
        }
366
367 407
        if ($this->file !== null) {
368 10
            $this->loadFile($this->file);
369
        }
370
371 407
        if ($this->env !== null) {
372 1
            $this->loadEnvironment($this->env);
373
        }
374
375 407
        if ($this->name !== null && $this->reference !== null) {
376
            // get the refereced property
377
            try {
378
                $referencedObject = $this->reference->getReferencedObject($this->project);
379
380
                if ($referencedObject instanceof Exception) {
381
                    $reference = $referencedObject->getMessage();
382
                } else {
383
                    $reference = (string) $referencedObject;
384
                }
385
386
                $this->addProperty($this->name, $reference);
387
            } catch (BuildException $be) {
388
                if ($this->fallback !== null) {
389
                    $referencedObject = $this->reference->getReferencedObject($this->fallback);
390
391
                    if ($referencedObject instanceof Exception) {
392
                        $reference = $referencedObject->getMessage();
393
                    } else {
394
                        $reference = (string) $referencedObject;
395
                    }
396
                    $this->addProperty($this->name, $reference);
397
                } else {
398
                    throw $be;
399
                }
400
            }
401
        }
402 407
    }
403
404
    /**
405
     * @throws BuildException
406
     */
407 407
    private function validate(): void
408
    {
409 407
        if ($this->name !== null) {
410 405
            if ($this->value === null && $this->reference === null) {
411
                throw new BuildException(
412
                    "You must specify value or refid with the name attribute",
413
                    $this->getLocation()
414
                );
415
            }
416 11
        } elseif ($this->file === null && $this->env === null) {
417
            throw new BuildException(
418
                "You must specify file or environment when not using the name attribute",
419
                $this->getLocation()
420
            );
421
        }
422
423 407
        if ($this->file === null && $this->prefix !== null) {
424
            throw new BuildException('Prefix is only valid when loading from a file.', $this->getLocation());
425
        }
426 407
    }
427
428
    /**
429
     * load the environment values
430
     *
431
     * @param string $prefix prefix to place before them
432
     */
433 1
    protected function loadEnvironment(string $prefix)
434
    {
435 1
        $props = new Properties();
436 1
        if (substr($prefix, strlen($prefix) - 1) === '.') {
437
            $prefix .= ".";
438
        }
439 1
        $this->log("Loading Environment $prefix", Project::MSG_VERBOSE);
440 1
        foreach ($_ENV as $key => $value) {
441
            $props->setProperty($prefix . '.' . $key, $value);
442
        }
443 1
        $this->addProperties($props);
444 1
    }
445
446
    /**
447
     * iterate through a set of properties,
448
     * resolve them then assign them
449
     *
450
     * @param Properties $props
451
     * @throws BuildException
452
     */
453 11
    protected function addProperties($props)
454
    {
455 11
        $this->resolveAllProperties($props);
456 11
        foreach ($props->keys() as $name) {
457 10
            $value = $props->getProperty($name);
458 10
            $v = $this->project->replaceProperties($value);
459 10
            if ($this->prefix !== null) {
460
                $name = $this->prefix . $name;
461
            }
462 10
            $this->addProperty($name, $v);
463
        }
464 11
    }
465
466
    /**
467
     * add a name value pair to the project property set
468
     *
469
     * @param string $name name of property
470
     * @param string $value value to set
471
     */
472 407
    protected function addProperty($name, $value)
473
    {
474 407
        if ($this->file === null && count($this->filterChains) > 0) {
475
            $in = FileUtils::getChainedReader(new StringReader($value), $this->filterChains, $this->project);
476
            $value = $in->read();
477
        }
478
479 407
        $ph = PropertyHelper::getPropertyHelper($this->getProject());
480 407
        if ($this->userProperty) {
481 10
            if ($ph->getUserProperty(null, $name) === null || $this->override) {
482 8
                $ph->setInheritedProperty(null, $name, $value);
483
            } else {
484 7
                $this->log('Override ignored for ' . $name, Project::MSG_VERBOSE);
485
            }
486
        } else {
487 404
            if ($this->override) {
488 2
                $ph->setProperty(null, $name, $value, true);
489
            } else {
490 404
                $ph->setNewProperty(null, $name, $value);
491
            }
492
        }
493 407
    }
494
495
    /**
496
     * load properties from a file.
497
     *
498
     * @param  PhingFile $file
499
     * @throws BuildException
500
     */
501 10
    protected function loadFile(PhingFile $file)
502
    {
503 10
        $fileParser = $this->fileParserFactory->createParser($file->getFileExtension());
504 10
        $props = new Properties(null, $fileParser);
505 10
        $this->log("Loading " . $file->getAbsolutePath(), $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE);
506
        try { // try to load file
507 10
            if ($file->exists()) {
508 10
                $value = null;
509 10
                if (count($this->filterChains) > 0) {
510 1
                    $in = FileUtils::getChainedReader(new FileReader($file), $this->filterChains, $this->project);
511 1
                    $value = $in->read();
512
                }
513 10
                if ($value) {
514 1
                    foreach (array_filter(explode(PHP_EOL, $value)) as $line) {
515 1
                        [$key, $prop] = explode('=', $line);
516 1
                        $props->setProperty($key, $prop);
517
                    }
518
                } else {
519 9
                    $props->load($file);
520
                }
521 10
                $this->addProperties($props);
522
            } else {
523
                if ($this->required) {
524
                    throw new BuildException("Unable to find property file: " . $file->getAbsolutePath());
525
                }
526
527
                $this->log(
528
                    "Unable to find property file: " . $file->getAbsolutePath() . "... skipped",
529
                    $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN
530
                );
531
            }
532
        } catch (IOException $ioe) {
533
            throw new BuildException("Could not load properties from file.", $ioe);
534
        }
535 10
    }
536
537
    /**
538
     * Given a Properties object, this method goes through and resolves
539
     * any references to properties within the object.
540
     *
541
     * @param  Properties $props The collection of Properties that need to be resolved.
542
     * @throws BuildException
543
     * @return void
544
     */
545 11
    protected function resolveAllProperties(Properties $props)
546
    {
547 11
        foreach ($props->keys() as $name) {
548
            // There may be a nice regex/callback way to handle this
549
            // replacement, but at the moment it is pretty complex, and
550
            // would probably be a lot uglier to work into a preg_replace_callback()
551
            // system.  The biggest problem is the fact that a resolution may require
552
            // multiple passes.
553
554 10
            $value = $props->getProperty($name);
555 10
            $resolved = false;
556 10
            $resolveStack = [];
557
558 10
            while (!$resolved) {
559 10
                $fragments = [];
560 10
                $propertyRefs = [];
561
562 10
                PropertyHelper::getPropertyHelper($this->project)->parsePropertyString(
563 10
                    $value,
564
                    $fragments,
565
                    $propertyRefs
566
                );
567
568 10
                $resolved = true;
569 10
                if (count($propertyRefs) === 0) {
570 10
                    continue;
571
                }
572
573
                $sb = "";
574
575
                $j = $propertyRefs;
576
577
                foreach ($fragments as $fragment) {
578
                    if ($fragment !== null) {
579
                        $sb .= $fragment;
580
                        continue;
581
                    }
582
583
                    $propertyName = array_shift($j);
584
                    if (in_array($propertyName, $resolveStack)) {
585
                        // Should we maybe just log this as an error & move on?
586
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
587
                        throw new BuildException("Property " . $propertyName . " was circularly defined.");
588
                    }
589
590
                    $fragment = $this->getProject()->getProperty($propertyName);
591
                    if ($fragment !== null) {
592
                        $sb .= $fragment;
593
                        continue;
594
                    }
595
596
                    if ($props->containsKey($propertyName)) {
597
                        $fragment = $props->getProperty($propertyName);
598
                        if (strpos($fragment, '${') !== false) {
599
                            $resolveStack[] = $propertyName;
600
                            $resolved = false; // parse again (could have been replaced w/ another var)
601
                        }
602
                    } else {
603
                        $fragment = "\${" . $propertyName . "}";
604
                    }
605
606
                    $sb .= $fragment;
607
                }
608
609
                $this->log("Resolved Property \"$value\" to \"$sb\"", Project::MSG_DEBUG);
610
                $value = $sb;
611
                $props->setProperty($name, $value);
612
            } // while (!$resolved)
613
        } // while (count($keys)
614 11
    }
615
}
616