Passed
Push — master ( 1117df...fac7df )
by Siad
11:02
created

PropertyHelper::replaceProperties()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 58
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8.0021

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 30
nc 7
nop 2
dl 0
loc 58
ccs 30
cts 31
cp 0.9677
crap 8.0021
rs 8.1954
c 1
b 0
f 0

How to fix   Long Method   

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
 * Component creation and configuration
22
 *
23
 * @author Siad Ardroumli <[email protected]>
24
 *
25
 * @package phing
26
 */
27
class PropertyHelper
28
{
29
    /**
30
     * @var Project $project
31
     */
32
    private $project;
33
    private $next;
34
35
    /**
36
     * Project properties map (usually String to String).
37
     */
38
    private $properties = [];
39
40
    /**
41
     * Map of "user" properties (as created in the Ant task, for example).
42
     * Note that these key/value pairs are also always put into the
43
     * project properties, so only the project properties need to be queried.
44
     * Mapping is String to String.
45
     */
46
    private $userProperties = [];
47
48
    /**
49
     * Map of inherited "user" properties - that are those "user"
50
     * properties that have been created by tasks and not been set
51
     * from the command line or a GUI tool.
52
     * Mapping is String to String.
53
     */
54
    private $inheritedProperties = [];
55
56
    // --------------------  Hook management  --------------------
57
58
    /**
59
     * Set the project for which this helper is performing property resolution
60
     *
61
     * @param Project $p the project instance.
62
     */
63 828
    private function setProject(Project $p)
64
    {
65 828
        $this->project = $p;
66 828
    }
67
68
    /**
69
     * There are 2 ways to hook into property handling:
70
     *  - you can replace the main PropertyHelper. The replacement is required
71
     * to support the same semantics (of course :-)
72
     *
73
     *  - you can chain a property helper capable of storing some properties.
74
     *  Again, you are required to respect the immutability semantics (at
75
     *  least for non-dynamic properties)
76
     *
77
     * @param PropertyHelper $next the next property helper in the chain.
78
     */
79
    public function setNext(PropertyHelper $next)
80
    {
81
        $this->next = $next;
82
    }
83
84
    /**
85
     * Get the next property helper in the chain.
86
     *
87
     * @return PropertyHelper the next property helper.
88
     */
89 826
    public function getNext()
90
    {
91 826
        return $this->next;
92
    }
93
94
    /**
95
     * Factory method to create a property processor.
96
     * Users can provide their own or replace it using "ant.PropertyHelper"
97
     * reference. User tasks can also add themselves to the chain, and provide
98
     * dynamic properties.
99
     *
100
     * @param Project $project the project fro which the property helper is required.
101
     *
102
     * @return PropertyHelper the project's property helper.
103
     */
104 828
    public static function getPropertyHelper(Project $project)
105
    {
106
        /**
107
         * @var PropertyHelper $helper
108
         */
109 828
        $helper = $project->getReference('phing.PropertyHelper');
110 828
        if ($helper !== null) {
111 810
            return $helper;
112
        }
113 828
        $helper = new self();
114 828
        $helper->setProject($project);
115
116 828
        $project->addReference('phing.PropertyHelper', $helper);
117 828
        return $helper;
118
    }
119
120
    // --------------------  Methods to override  --------------------
121
122
    /**
123
     * Sets a property. Any existing property of the same name
124
     * is overwritten, unless it is a user property. Will be called
125
     * from setProperty().
126
     *
127
     * If all helpers return false, the property will be saved in
128
     * the default properties table by setProperty.
129
     *
130
     * @param  string $ns The namespace that the property is in (currently
131
     *                          not used.
132
     * @param  string $name The name of property to set.
133
     *                          Must not be
134
     *                          <code>null</code>.
135
     * @param  string $value The new value of the property.
136
     *                          Must not be <code>null</code>.
137
     * @param  bool $inherited True if this property is inherited (an [sub]ant[call] property).
138
     * @param  bool $user True if this property is a user property.
139
     * @param  bool $isNew True is this is a new property.
140
     * @return bool true if this helper has stored the property, false if it
141
     *    couldn't. Each helper should delegate to the next one (unless it
142
     *    has a good reason not to).
143
     */
144 825
    public function setPropertyHook($ns, $name, $value, $inherited, $user, $isNew)
145
    {
146 825
        return $this->getNext() !== null
147 825
            && $this->getNext()->setPropertyHook($ns, $name, $value, $inherited, $user, $isNew);
148
    }
149
150
    /**
151
     * Get a property. If all hooks return null, the default
152
     * tables will be used.
153
     *
154
     * @param  string $ns namespace of the sought property.
155
     * @param  string $name name of the sought property.
156
     * @param  bool $user True if this is a user property.
157
     * @return string The property, if returned by a hook, or null if none.
158
     */
159 810
    public function getPropertyHook($ns, $name, $user)
160
    {
161 810
        if ($this->getNext() !== null) {
162
            $o = $this->getNext()->getPropertyHook($ns, $name, $user);
163
            if ($o !== null) {
0 ignored issues
show
introduced by
The condition $o !== null is always true.
Loading history...
164
                return $o;
165
            }
166
        }
167
168 810
        if ($this->project !== null && StringHelper::startsWith('toString:', $name)) {
169 1
            $name = StringHelper::substring($name, strlen('toString:'));
170 1
            $v = $this->project->getReference($name);
171 1
            return ($v === null) ? null : (string) $v;
172
        }
173
174 810
        return null;
175
    }
176
177
    // -------------------- Optional methods   --------------------
178
    // You can override those methods if you want to optimize or
179
    // do advanced things (like support a special syntax).
180
    // The methods do not chain - you should use them when embedding ant
181
    // (by replacing the main helper)
182
183
    /**
184
     * Replaces <code>${xxx}</code> style constructions in the given value
185
     * with the string value of the corresponding data types.
186
     *
187
     * @param string $value The string to be scanned for property references.
188
     *                        May be <code>null</code>, in which case this
189
     *                        method returns immediately with no effect.
190
     * @param string[] $keys Mapping (String to String) of property names to their
191
     *              values. If <code>null</code>, only project properties will
192
     *              be used.
193
     *
194
     * @return    string the original string with the properties replaced, or
195
     *         <code>null</code> if the original string is <code>null</code>.
196
     * @throws BuildException if the string contains an opening
197
     *                           <code>${</code> without a closing
198
     *                           <code>}</code>
199
     */
200 663
    public function replaceProperties($value, $keys): ?string
201
    {
202 663
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
203
            return null;
204
        }
205 663
        if ($keys === null) {
0 ignored issues
show
introduced by
The condition $keys === null is always false.
Loading history...
206 1
            $keys = $this->project->getProperties();
207
        }
208
        // Because we're not doing anything special (like multiple passes),
209
        // regex is the simplest / fastest.  PropertyTask, though, uses
210
        // the old parsePropertyString() method, since it has more stringent
211
        // requirements.
212
213 663
        $sb = $value;
214 663
        $iteration = 0;
215
        // loop to recursively replace tokens
216 663
        while (strpos($sb, '${') !== false) {
217 393
            $sb = preg_replace_callback(
218 393
                '/\$\{([^\$}]+)\}/',
219
                function ($matches) use ($keys) {
220 393
                    $propertyName = $matches[1];
221
222 393
                    $replacement = null;
223 393
                    if (array_key_exists($propertyName, $keys)) {
224 386
                        $replacement = $keys[$propertyName];
225
                    }
226
227 393
                    if ($replacement === null) {
228 11
                        $replacement = $this->getProperty(null, $propertyName);
229
                    }
230
231 393
                    if ($replacement === null) {
0 ignored issues
show
introduced by
The condition $replacement === null is always false.
Loading history...
232 10
                        $this->project->log(
233 10
                            'Property ${' . $propertyName . '} has not been set.',
234 10
                            Project::MSG_VERBOSE
235
                        );
236
237 10
                        return $matches[0];
238
                    }
239
240 387
                    $this->project->log(
241 387
                        'Property ${' . $propertyName . '} => ' . (string) $replacement,
242 387
                        Project::MSG_VERBOSE
243
                    );
244
245 387
                    return $replacement;
246 393
                },
247 393
                $sb
248
            );
249
250
            // keep track of iterations so we can break out of otherwise infinite loops.
251 393
            $iteration++;
252 393
            if ($iteration === 5) {
253 10
                return $sb;
254
            }
255
        }
256
257 662
        return $sb;
258
    }
259
260
    // -------------------- Default implementation  --------------------
261
    // Methods used to support the default behavior and provide backward
262
    // compatibility. Some will be deprecated, you should avoid calling them.
263
264
265
    /**
266
     * Default implementation of setProperty. Will be called from Project.
267
     *  This is the original 1.5 implementation, with calls to the hook
268
     *  added.
269
     *
270
     * @param  string $ns The namespace for the property (currently not used).
271
     * @param  string $name The name of the property.
272
     * @param  string $value The value to set the property to.
273
     * @param  bool $verbose If this is true output extra log messages.
274
     * @return bool true if the property is set.
275
     */
276 825
    public function setProperty($ns, $name, $value, $verbose)
277
    {
278
        // user (CLI) properties take precedence
279 825
        if (isset($this->userProperties[$name])) {
280 23
            if ($verbose) {
281
                $this->project->log('Override ignored for user property ' . $name, Project::MSG_VERBOSE);
282
            }
283 23
            return false;
284
        }
285
286 825
        $done = $this->setPropertyHook($ns, $name, $value, false, false, false);
287 825
        if ($done) {
288
            return true;
289
        }
290
291 825
        if ($verbose && isset($this->properties[$name])) {
292 2
            $this->project->log(
293 2
                'Overriding previous definition of property ' . $name,
294 2
                Project::MSG_VERBOSE
295
            );
296
        }
297
298 825
        if ($verbose) {
299 208
            $this->project->log(
300 208
                'Setting project property: ' . $name . " -> "
301 208
                . $value,
302 208
                Project::MSG_DEBUG
303
            );
304
        }
305 825
        $this->properties[$name] = $value;
306 825
        $this->project->addReference($name, new PropertyValue($value));
307 825
        return true;
308
    }
309
310
    /**
311
     * Sets a property if no value currently exists. If the property
312
     * exists already, a message is logged and the method returns with
313
     * no other effect.
314
     *
315
     * @param string $ns The namespace for the property (currently not used).
316
     * @param string $name The name of property to set.
317
     *                      Must not be
318
     *                      <code>null</code>.
319
     * @param string $value The new value of the property.
320
     *              Must not be <code>null</code>.
321
     */
322 468
    public function setNewProperty($ns, $name, $value)
323
    {
324 468
        if (isset($this->properties[$name])) {
325 6
            $this->project->log('Override ignored for property ' . $name, Project::MSG_VERBOSE);
326 6
            return;
327
        }
328
329 466
        $done = $this->setPropertyHook($ns, $name, $value, false, false, true);
330 466
        if ($done) {
331
            return;
332
        }
333
334 466
        $this->project->log('Setting project property: ' . $name . " -> " . $value, Project::MSG_DEBUG);
335 466
        if ($name !== null && $value !== null) {
0 ignored issues
show
introduced by
The condition $value !== null is always true.
Loading history...
336 466
            $this->properties[$name] = $value;
337 466
            $this->project->addReference($name, new PropertyValue($value));
338
        }
339 466
    }
340
341
    /**
342
     * Sets a user property, which cannot be overwritten by
343
     * set/unset property calls. Any previous value is overwritten.
344
     *
345
     * @param string $ns The namespace for the property (currently not used).
346
     * @param string $name The name of property to set.
347
     *                      Must not be
348
     *                      <code>null</code>.
349
     * @param string $value The new value of the property.
350
     *              Must not be <code>null</code>.
351
     */
352 809
    public function setUserProperty($ns, $name, $value)
353
    {
354 809
        if ($name === null || $value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
355
            return;
356
        }
357 809
        $this->project->log('Setting ro project property: ' . $name . ' -> ' . $value, Project::MSG_DEBUG);
358 809
        $this->userProperties[$name] = $value;
359
360 809
        $done = $this->setPropertyHook($ns, $name, $value, false, true, false);
361 809
        if ($done) {
362
            return;
363
        }
364 809
        $this->properties[$name] = $value;
365 809
        $this->project->addReference($name, new PropertyValue($value));
366 809
    }
367
368
    /**
369
     * Sets an inherited user property, which cannot be overwritten by set/unset
370
     * property calls. Any previous value is overwritten. Also marks
371
     * these properties as properties that have not come from the
372
     * command line.
373
     *
374
     * @param string $ns The namespace for the property (currently not used).
375
     * @param string $name The name of property to set.
376
     *                      Must not be
377
     *                      <code>null</code>.
378
     * @param string $value The new value of the property.
379
     *              Must not be <code>null</code>.
380
     */
381 22
    public function setInheritedProperty($ns, $name, $value)
382
    {
383 22
        if ($name === null || $value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
384
            return;
385
        }
386 22
        $this->inheritedProperties[$name] = $value;
387
388 22
        $this->project->log(
389 22
            "Setting ro project property: " . $name . " -> "
390 22
            . $value,
391 22
            Project::MSG_DEBUG
392
        );
393 22
        $this->userProperties[$name] = $value;
394
395 22
        $done = $this->setPropertyHook($ns, $name, $value, true, false, false);
396 22
        if ($done) {
397
            return;
398
        }
399 22
        $this->properties[$name] = $value;
400 22
        $this->project->addReference($name, new PropertyValue($value));
401 22
    }
402
403
    // -------------------- Getting properties  --------------------
404
405
    /**
406
     * Returns the value of a property, if it is set.  You can override
407
     * this method in order to plug your own storage.
408
     *
409
     * @param  string $ns The namespace for the property (currently not used).
410
     * @param  string $name The name of the property.
411
     *             May be <code>null</code>, in which case
412
     *             the return value is also <code>null</code>.
413
     * @return string the property value, or <code>null</code> for no match
414
     *         or if a <code>null</code> name is provided.
415
     */
416 810
    public function getProperty($ns, $name)
417
    {
418 810
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
419
            return null;
420
        }
421 810
        $o = $this->getPropertyHook($ns, $name, false);
422 810
        if ($o !== null) {
0 ignored issues
show
introduced by
The condition $o !== null is always true.
Loading history...
423 1
            return $o;
424
        }
425
426 810
        $found = $this->properties[$name] ?? null;
427
        // check to see if there are unresolved property references
428 810
        if (false !== strpos($found, '${')) {
429
            // attempt to resolve properties
430 1
            $found = $this->replaceProperties($found, null);
431
            // save resolved value
432 1
            $this->properties[$name] = $found;
433
        }
434
435 810
        return $found;
436
    }
437
438
    /**
439
     * Returns the value of a user property, if it is set.
440
     *
441
     * @param  string $ns The namespace for the property (currently not used).
442
     * @param  string $name The name of the property.
443
     *             May be <code>null</code>, in which case
444
     *             the return value is also <code>null</code>.
445
     * @return string the property value, or <code>null</code> for no match
446
     *         or if a <code>null</code> name is provided.
447
     */
448 13
    public function getUserProperty($ns, $name)
449
    {
450 13
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
451
            return null;
452
        }
453 13
        $o = $this->getPropertyHook($ns, $name, true);
454 13
        if ($o !== null) {
0 ignored issues
show
introduced by
The condition $o !== null is always true.
Loading history...
455
            return $o;
456
        }
457 13
        return $this->userProperties[$name] ?? null;
458
    }
459
460
461
    // -------------------- Access to property tables  --------------------
462
    // This is used to support ant call and similar tasks. It should be
463
    // deprecated, it is possible to use a better (more efficient)
464
    // mechanism to preserve the context.
465
466
    /**
467
     * Returns a copy of the properties table.
468
     *
469
     * @return array a hashtable containing all properties
470
     *         (including user properties).
471
     */
472 810
    public function getProperties()
473
    {
474 810
        return $this->properties;
475
    }
476
477
    /**
478
     * Returns a copy of the user property hashtable
479
     *
480
     * @return array a hashtable containing just the user properties
481
     */
482
    public function getUserProperties()
483
    {
484
        return $this->userProperties;
485
    }
486
487
    public function getInheritedProperties()
488
    {
489
        return $this->inheritedProperties;
490
    }
491
492
    /**
493
     * Copies all user properties that have not been set on the
494
     * command line or a GUI tool from this instance to the Project
495
     * instance given as the argument.
496
     *
497
     * <p>To copy all "user" properties, you will also have to call
498
     * {@link #copyUserProperties copyUserProperties}.</p>
499
     *
500
     * @param Project $other the project to copy the properties to.  Must not be null.
501
     */
502
    public function copyInheritedProperties(Project $other)
503
    {
504
        foreach ($this->inheritedProperties as $arg => $value) {
505
            if ($other->getUserProperty($arg) === null) {
506
                $other->setInheritedProperty($arg, (string) $this->inheritedProperties[$arg]);
507
            }
508
        }
509
    }
510
511
    /**
512
     * Copies all user properties that have been set on the command
513
     * line or a GUI tool from this instance to the Project instance
514
     * given as the argument.
515
     *
516
     * <p>To copy all "user" properties, you will also have to call
517
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
518
     *
519
     * @param Project $other the project to copy the properties to.  Must not be null.
520
     */
521 33
    public function copyUserProperties(Project $other)
522
    {
523 33
        foreach ($this->userProperties as $arg => $value) {
524 33
            if (!isset($this->inheritedProperties[$arg])) {
525 33
                $other->setUserProperty($arg, $value);
526
            }
527
        }
528 33
    }
529
530
    /**
531
     * Parses a string containing <code>${xxx}</code> style property
532
     * references into two lists. The first list is a collection
533
     * of text fragments, while the other is a set of string property names.
534
     * <code>null</code> entries in the first list indicate a property
535
     * reference from the second list.
536
     *
537
     * It can be overridden with a more efficient or customized version.
538
     *
539
     * @param string $value Text to parse. Must not be <code>null</code>.
540
     * @param array $fragments List to add text fragments to.
541
     *                             Must not be <code>null</code>.
542
     * @param array $propertyRefs List to add property names to.
543
     *                             Must not be <code>null</code>.
544
     *
545
     * @throws BuildException if the string contains an opening
546
     *                           <code>${</code> without a closing
547
     *                           <code>}</code>
548
     */
549 17
    public function parsePropertyString($value, &$fragments, &$propertyRefs)
550
    {
551 17
        $prev = 0;
552 17
        $pos = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $pos is dead and can be removed.
Loading history...
553
554 17
        while (($pos = strpos($value, '$', $prev)) !== false) {
555 7
            if ($pos > $prev) {
556 5
                $fragments[] = StringHelper::substring($value, $prev, $pos - 1);
557
            }
558 7
            if ($pos === (strlen($value) - 1)) {
559
                $fragments[] = '$';
560
                $prev = $pos + 1;
561 7
            } elseif ($value[$pos + 1] !== '{') {
562
                // the string positions were changed to value-1 to correct
563
                // a fatal error coming from function substring()
564
                $fragments[] = StringHelper::substring($value, $pos, $pos + 1);
565
                $prev = $pos + 2;
566
            } else {
567 7
                $endName = strpos($value, '}', $pos);
568 7
                if ($endName === false) {
569
                    throw new BuildException("Syntax error in property: $value");
570
                }
571 7
                $propertyName = StringHelper::substring($value, $pos + 2, $endName - 1);
572 7
                $fragments[] = null;
573 7
                $propertyRefs[] = $propertyName;
574 7
                $prev = $endName + 1;
575
            }
576
        }
577
578 17
        if ($prev < strlen($value)) {
579 17
            $fragments[] = StringHelper::substring($value, $prev);
580
        }
581 17
    }
582
}
583