Passed
Push — master ( b5ca42...8fe651 )
by Siad
05:05
created

PropertyTask::resolveAllProperties()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 67
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 10.0021

Importance

Changes 0
Metric Value
cc 10
eloc 38
nc 10
nop 1
dl 0
loc 67
ccs 35
cts 36
cp 0.9722
crap 10.0021
rs 7.6666
c 0
b 0
f 0

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

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

614
                        if (strpos(/** @scrutinizer ignore-type */ $fragment, '${') !== false) {
Loading history...
615 3
                            $resolveStack[] = $propertyName;
616 5
                            $resolved = false; // parse again (could have been replaced w/ another var)
617
                        }
618
                    } else {
619
                        $fragment = "\${" . $propertyName . "}";
620
                    }
621
622 5
                    $sb .= $fragment;
623
                }
624
625 6
                $this->log("Resolved Property \"$value\" to \"$sb\"", Project::MSG_DEBUG);
626 6
                $value = $sb;
627 6
                $props->setProperty($name, $value);
628
            } // while (!$resolved)
629
        } // while (count($keys)
630 15
    }
631
}
632