Passed
Push — master ( 7a9799...2aee06 )
by Michiel
06:03
created

Variable::removeProperty()   A

Complexity

Conditions 5
Paths 25

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 18
rs 9.5222
c 0
b 0
f 0
ccs 0
cts 12
cp 0
cc 5
nc 25
nop 1
crap 30
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Task\System\Property;
21
22
use Exception;
23
use Phing\Exception\BuildException;
24
use Phing\Io\IOException;
25
use Phing\Io\File;
26
use Phing\Project;
27
use Phing\PropertyHelper;
28
use Phing\Task\System\PropertyTask;
29
use Phing\Util\Properties;
30
use ReflectionObject;
31
use ReflectionProperty;
32
33
/**
34
 * Variable Task.
35
 *
36
 * @author  Siad Ardroumli <[email protected]>
37
 * @package phing.tasks.ext.property
38
 */
39
class Variable extends PropertyTask
40
{
41
    private $remove = false;
42
43
    /**
44
     * Determines whether the property should be removed from the project.
45
     * Default is false. Once  removed, conditions that check for property
46
     * existence will find this property does not exist.
47
     *
48
     * @param boolean $b set to true to remove the property from the project.
49
     */
50
    public function setUnset($b)
51
    {
52
        $this->remove = $b;
53
    }
54
55
    /**
56
     * Execute this task.
57
     *
58
     * @throws BuildException  Description of the Exception
59
     */
60 1
    public function main()
61
    {
62 1
        if ($this->remove) {
63
            if ($this->name === null || $this->name === "") {
64
                throw new BuildException("The 'name' attribute is required with 'unset'.");
65
            }
66
            $this->removeProperty($this->name);
67
            return;
68
        }
69 1
        if ($this->file === null) {
70
            // check for the required name attribute
71 1
            if ($this->name === null || $this->name === '') {
72
                throw new BuildException("The 'name' attribute is required.");
73
            }
74
75
            // adjust the property value if necessary -- is this necessary?
76
            // Doesn't Ant do this automatically?
77 1
            $this->value = $this->getProject()->replaceProperties($this->value);
78
79
            // set the property
80 1
            $this->forceProperty($this->name, $this->value);
81
        } else {
82 1
            if (!$this->file->exists()) {
83
                throw new BuildException($this->file->getAbsolutePath() . " does not exists.");
84
            }
85 1
            $this->loadFile($this->file);
86
        }
87 1
    }
88
89
    /**
90
     * Remove a property from the project's property table and the userProperty table.
91
     * Note that Ant 1.6 uses a helper for this.
92
     */
93
    private function removeProperty($name)
94
    {
95
        $properties = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $properties is dead and can be removed.
Loading history...
96
        try {
97
            $properties = $this->getPropValue($this->getProject(), 'properties');
98
            if ($properties !== null) {
99
                unset($properties[$name]);
100
                $this->setPropValue($properties, $this->getProject(), 'properties');
101
            }
102
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
103
        }
104
        try {
105
            $properties = $this->getPropValue($this->getProject(), 'userProperties');
106
            if ($properties !== null) {
107
                unset($properties[$name]);
108
                $this->setPropValue($properties, $this->getProject(), 'userProperties');
109
            }
110
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
111
        }
112
    }
113
114 1
    private function forceProperty($name, $value)
115
    {
116
        try {
117 1
            $properties = $this->getPropValue($this->getProject(), 'properties');
118
            if ($properties === null) {
119
                $this->getProject()->setUserProperty($name, $value);
120
            } else {
121
                $properties[$name] = $value;
122
                $this->setPropValue($properties, $this->getProject(), 'properties');
123
            }
124 1
        } catch (Exception $e) {
125 1
            $this->getProject()->setUserProperty($name, $value);
126
        }
127 1
    }
128
129
    /**
130
     * Get a private property of a class
131
     *
132
     * @param mixed $thisClass The class
133
     * @param string $fieldName The property to get
134
     * @return ReflectionProperty               The property value
135
     * @throws Exception
136
     */
137 1
    private function getField($thisClass, $fieldName)
138
    {
139 1
        $refClazz = new ReflectionObject($thisClass);
140 1
        if (!$refClazz->hasProperty($fieldName)) {
141 1
            throw new Exception("Invalid field : $fieldName");
142
        }
143
144
        return $refClazz->getProperty($fieldName);
145
    }
146
147
    /**
148
     * Get a private property of an object
149
     *
150
     * @param mixed $instance the object instance
151
     * @param string $fieldName the name of the field
152
     * @return mixed an object representing the value of the field
153
     * @throws Exception
154
     */
155 1
    private function getPropValue($instance, $fieldName)
156
    {
157 1
        $field = $this->getField($instance, $fieldName);
158
        $field->setAccessible(true);
159
        return $field->getValue($instance);
160
    }
161
162
    private function setPropValue($value, $instance, $fieldName)
163
    {
164
        $field = $this->getField($instance, $fieldName);
165
        $field->setAccessible(true);
166
        $field->setValue($instance, $value);
167
    }
168
169
    /**
170
     * load variables from a file
171
     *
172
     * @param File $file file to load
173
     * @throws BuildException
174
     */
175 1
    protected function loadFile(File $file)
176
    {
177 1
        $props = new Properties();
178
        try {
179 1
            if ($file->exists()) {
180 1
                $props->load($file);
181
182 1
                $this->addProperties($props);
183
            } else {
184
                $this->log(
185
                    'Unable to find property file: ' . $file->getAbsolutePath(),
186 1
                    Project::MSG_VERBOSE
187
                );
188
            }
189
        } catch (IOException $ex) {
190
            throw new BuildException($ex, $this->getLocation());
191
        }
192 1
    }
193
194
    /**
195
     * iterate through a set of properties, resolve them, then assign them
196
     *
197
     * @param Properties $props The feature to be added to the Properties attribute
198
     */
199 1
    protected function addProperties($props)
200
    {
201 1
        $this->resolveAllProperties($props);
202 1
        foreach ($props->keys() as $name) {
203 1
            $this->forceProperty($name, $props->getProperty($name));
204
        }
205 1
    }
206
207
    /**
208
     * resolve properties inside a properties hashtable
209
     *
210
     * @param Properties $props properties object to resolve
211
     * @throws BuildException  Description of the Exception
212
     */
213 1
    protected function resolveAllProperties(Properties $props)
214
    {
215 1
        foreach ($props->keys() as $name) {
216
            // There may be a nice regex/callback way to handle this
217
            // replacement, but at the moment it is pretty complex, and
218
            // would probably be a lot uglier to work into a preg_replace_callback()
219
            // system.  The biggest problem is the fact that a resolution may require
220
            // multiple passes.
221
222 1
            $value = $props->getProperty($name);
223 1
            $resolved = false;
224 1
            $resolveStack = array();
225
226 1
            $ih = PropertyHelper::getPropertyHelper($this->project);
227
228 1
            while (!$resolved) {
229 1
                $fragments = array();
230 1
                $propertyRefs = array();
231
232
                // [HL] this was ::parsePropertyString($this->value ...) ... this seems wrong
233 1
                $ih->parsePropertyString($value, $fragments, $propertyRefs);
234
235 1
                $resolved = true;
236 1
                if (count($propertyRefs) == 0) {
237 1
                    continue;
238
                }
239
240 1
                $sb = "";
241
242 1
                $j = $propertyRefs;
243
244 1
                foreach ($fragments as $fragment) {
245 1
                    if ($fragment !== null) {
246 1
                        $sb .= $fragment;
247 1
                        continue;
248
                    }
249
250 1
                    $propertyName = array_shift($j);
251 1
                    if (in_array($propertyName, $resolveStack)) {
252
                        // Should we maybe just log this as an error & move on?
253
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
254
                        throw new BuildException("Property " . $propertyName . " was circularly defined.");
255
                    }
256
257 1
                    $fragment = $this->getProject()->getProperty($propertyName);
258 1
                    if ($fragment !== null) {
259 1
                        $sb .= $fragment;
260 1
                        continue;
261
                    }
262
263 1
                    if ($props->containsKey($propertyName)) {
264 1
                        $fragment = $props->getProperty($propertyName);
265 1
                        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

265
                        if (strpos(/** @scrutinizer ignore-type */ $fragment, '${') !== false) {
Loading history...
266
                            $resolveStack[] = $propertyName;
267 1
                            $resolved = false; // parse again (could have been replaced w/ another var)
268
                        }
269
                    } else {
270
                        $fragment = "\${" . $propertyName . "}";
271
                    }
272
273 1
                    $sb .= $fragment;
274
                }
275
276 1
                $this->log("Resolved Property \"$value\" to \"$sb\"", Project::MSG_DEBUG);
277 1
                $value = $sb;
278 1
                $props->setProperty($name, $value);
279
280 1
                $this->getProject()->setProperty($name, $value);
281
            }
282
        }
283 1
    }
284
}
285