Test Failed
Push — master ( 8c2ebd...43baba )
by Siad
25:13
created

PropertyHelper::replaceProperties()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 58
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 8.0032

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 30
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 58
ccs 26
cts 27
cp 0.963
crap 8.0032
rs 8.1954

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 794
    private function setProject(Project $p)
64
    {
65 794
        $this->project = $p;
66 794
    }
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 792
    public function getNext()
90
    {
91 792
        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 794
    public static function getPropertyHelper(Project $project)
105
    {
106
        /**
107
         * @var PropertyHelper $helper
108
         */
109 794
        $helper = $project->getReference('phing.PropertyHelper');
110 794
        if ($helper !== null) {
111 776
            return $helper;
112
        }
113 794
        $helper = new self();
114 794
        $helper->setProject($project);
115
116 794
        $project->addReference('phing.PropertyHelper', $helper);
117 794
        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 791
    public function setPropertyHook($ns, $name, $value, $inherited, $user, $isNew)
145
    {
146 791
        return $this->getNext() !== null
147
            && $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 791
     * @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
    public function getPropertyHook($ns, $name, $user)
160
    {
161
        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 776
        }
167
168 776
        if ($this->project !== null && StringHelper::startsWith('toString:', $name)) {
169
            $name = StringHelper::substring($name, strlen('toString:'));
170
            $v = $this->project->getReference($name);
171
            return ($v === null) ? null : (string) $v;
172
        }
173
174
        return null;
175 776
    }
176 1
177 1
    // -------------------- Optional methods   --------------------
178 1
    // 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 776
    // (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
    public function replaceProperties($value, $keys): ?string
201
    {
202
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
203
            return null;
204
        }
205
        if ($keys === null) {
0 ignored issues
show
introduced by
The condition $keys === null is always false.
Loading history...
206
            $keys = $this->project->getProperties();
207 629
        }
208
        // Because we're not doing anything special (like multiple passes),
209 629
        // regex is the simplest / fastest.  PropertyTask, though, uses
210
        // the old parsePropertyString() method, since it has more stringent
211
        // requirements.
212 629
213 2
        $sb = $value;
214
        $iteration = 0;
215
        // loop to recursively replace tokens
216
        while (strpos($sb, '${') !== false) {
217
            $sb = preg_replace_callback(
218
                '/\$\{([^\$}]+)\}/',
219
                function ($matches) use ($keys) {
220 629
                    $propertyName = $matches[1];
221 629
222
                    $replacement = null;
223 629
                    if (array_key_exists($propertyName, $keys)) {
224 371
                        $replacement = $keys[$propertyName];
225 371
                    }
226
227 371
                    if ($replacement === null) {
228
                        $replacement = $this->getProperty(null, $propertyName);
229 371
                    }
230 371
231 364
                    if ($replacement === null) {
0 ignored issues
show
introduced by
The condition $replacement === null is always false.
Loading history...
232
                        $this->project->log(
233
                            'Property ${' . $propertyName . '} has not been set.',
234 371
                            Project::MSG_VERBOSE
235 12
                        );
236
237
                        return $matches[0];
238 371
                    }
239 11
240 11
                    $this->project->log(
241 11
                        'Property ${' . $propertyName . '} => ' . (string) $replacement,
242
                        Project::MSG_VERBOSE
243
                    );
244 11
245
                    return $replacement;
246
                },
247 365
                $sb
248 365
            );
249 365
250
            // keep track of iterations so we can break out of otherwise infinite loops.
251
            $iteration++;
252 365
            if ($iteration === 5) {
253 371
                return $sb;
254 371
            }
255
        }
256
257
        return $sb;
258 371
    }
259 371
260 11
    // -------------------- 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 628
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
    public function setProperty($ns, $name, $value, $verbose)
277
    {
278
        // user (CLI) properties take precedence
279
        if (isset($this->userProperties[$name])) {
280
            if ($verbose) {
281
                $this->project->log('Override ignored for user property ' . $name, Project::MSG_VERBOSE);
282
            }
283 791
            return false;
284
        }
285
286 791
        $done = $this->setPropertyHook($ns, $name, $value, false, false, false);
287 7
        if ($done) {
288
            return true;
289
        }
290 7
291
        if ($verbose && isset($this->properties[$name])) {
292
            $this->project->log(
293 791
                'Overriding previous definition of property ' . $name,
294 791
                Project::MSG_VERBOSE
295
            );
296
        }
297
298 791
        if ($verbose) {
299 2
            $this->project->log(
300 2
                'Setting project property: ' . $name . " -> "
301 2
                . $value,
302
                Project::MSG_DEBUG
303
            );
304
        }
305 791
        $this->properties[$name] = $value;
306 171
        $this->project->addReference($name, new PropertyValue($value));
307 171
        return true;
308 171
    }
309 171
310
    /**
311
     * Sets a property if no value currently exists. If the property
312 791
     * exists already, a message is logged and the method returns with
313 791
     * no other effect.
314 791
     *
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
    public function setNewProperty($ns, $name, $value)
323
    {
324
        if (isset($this->properties[$name])) {
325
            $this->project->log('Override ignored for property ' . $name, Project::MSG_VERBOSE);
326
            return;
327
        }
328
329 437
        $done = $this->setPropertyHook($ns, $name, $value, false, false, true);
330
        if ($done) {
331 437
            return;
332 2
        }
333 2
334
        $this->project->log('Setting project property: ' . $name . " -> " . $value, Project::MSG_DEBUG);
335
        if ($name !== null && $value !== null) {
0 ignored issues
show
introduced by
The condition $value !== null is always true.
Loading history...
336 436
            $this->properties[$name] = $value;
337 436
            $this->project->addReference($name, new PropertyValue($value));
338
        }
339
    }
340
341 436
    /**
342 436
     * Sets a user property, which cannot be overwritten by
343 436
     * set/unset property calls. Any previous value is overwritten.
344 436
     *
345
     * @param string $ns The namespace for the property (currently not used).
346 436
     * @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
    public function setUserProperty($ns, $name, $value)
353
    {
354
        if ($name === null || $value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
355
            return;
356
        }
357
        $this->project->log('Setting ro project property: ' . $name . ' -> ' . $value, Project::MSG_DEBUG);
358
        $this->userProperties[$name] = $value;
359 775
360
        $done = $this->setPropertyHook($ns, $name, $value, false, true, false);
361 775
        if ($done) {
362
            return;
363
        }
364 775
        $this->properties[$name] = $value;
365 775
        $this->project->addReference($name, new PropertyValue($value));
366
    }
367 775
368 775
    /**
369
     * Sets an inherited user property, which cannot be overwritten by set/unset
370
     * property calls. Any previous value is overwritten. Also marks
371 775
     * these properties as properties that have not come from the
372 775
     * command line.
373 775
     *
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
    public function setInheritedProperty($ns, $name, $value)
382
    {
383
        if ($name === null || $value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
384
            return;
385
        }
386
        $this->inheritedProperties[$name] = $value;
387
388 7
        $this->project->log(
389
            "Setting ro project property: " . $name . " -> "
390 7
            . $value,
391
            Project::MSG_DEBUG
392
        );
393 7
        $this->userProperties[$name] = $value;
394
395 7
        $done = $this->setPropertyHook($ns, $name, $value, true, false, false);
396 7
        if ($done) {
397 7
            return;
398 7
        }
399
        $this->properties[$name] = $value;
400 7
        $this->project->addReference($name, new PropertyValue($value));
401
    }
402 7
403 7
    // -------------------- Getting properties  --------------------
404
405
    /**
406 7
     * Returns the value of a property, if it is set.  You can override
407 7
     * this method in order to plug your own storage.
408 7
     *
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
    public function getProperty($ns, $name)
417
    {
418
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
419
            return null;
420
        }
421
        $o = $this->getPropertyHook($ns, $name, false);
422
        if ($o !== null) {
0 ignored issues
show
introduced by
The condition $o !== null is always true.
Loading history...
423 776
            return $o;
424
        }
425 776
426
        $found = $this->properties[$name] ?? null;
427
        // check to see if there are unresolved property references
428 776
        if (false !== strpos($found, '${')) {
429 776
            // attempt to resolve properties
430 1
            $found = $this->replaceProperties($found, null);
431
            // save resolved value
432
            $this->properties[$name] = $found;
433 776
        }
434
435 776
        return $found;
436
    }
437 2
438
    /**
439 2
     * 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 776
     * @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
    public function getUserProperty($ns, $name)
449
    {
450
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
451
            return null;
452
        }
453
        $o = $this->getPropertyHook($ns, $name, true);
454
        if ($o !== null) {
0 ignored issues
show
introduced by
The condition $o !== null is always true.
Loading history...
455 8
            return $o;
456
        }
457 8
        return $this->userProperties[$name] ?? null;
458
    }
459
460 8
461 8
    // -------------------- 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 8
    // 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
    public function getProperties()
473
    {
474
        return $this->properties;
475
    }
476
477
    /**
478
     * Returns a copy of the user property hashtable
479 776
     *
480
     * @return array a hashtable containing just the user properties
481 776
     */
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
    public function copyUserProperties(Project $other)
522
    {
523
        foreach ($this->userProperties as $arg => $value) {
524
            if (!isset($this->inheritedProperties[$arg])) {
525 8
                $other->setUserProperty($arg, $value);
526
            }
527 8
        }
528 8
    }
529
530
    /**
531 8
     * Parses a string containing <code>${xxx}</code> style property
532
     * references into two lists. The first list is a collection
533 8
     * 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
    public function parsePropertyString($value, &$fragments, &$propertyRefs)
550
    {
551
        $prev = 0;
552
        $pos = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $pos is dead and can be removed.
Loading history...
553
554 16
        while (($pos = strpos($value, '$', $prev)) !== false) {
555
            if ($pos > $prev) {
556 16
                $fragments[] = StringHelper::substring($value, $prev, $pos - 1);
557 16
            }
558
            if ($pos === (strlen($value) - 1)) {
559 16
                $fragments[] = '$';
560 7
                $prev = $pos + 1;
561 5
            } elseif ($value[$pos + 1] !== '{') {
562
                // the string positions were changed to value-1 to correct
563 7
                // a fatal error coming from function substring()
564
                $fragments[] = StringHelper::substring($value, $pos, $pos + 1);
565
                $prev = $pos + 2;
566 7
            } else {
567
                $endName = strpos($value, '}', $pos);
568
                if ($endName === false) {
569
                    throw new BuildException("Syntax error in property: $value");
570
                }
571
                $propertyName = StringHelper::substring($value, $pos + 2, $endName - 1);
572 7
                $fragments[] = null;
573 7
                $propertyRefs[] = $propertyName;
574
                $prev = $endName + 1;
575
            }
576 7
        }
577 7
578 7
        if ($prev < strlen($value)) {
579 7
            $fragments[] = StringHelper::substring($value, $prev);
580
        }
581
    }
582
}
583