Accessor::getAttributes()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * Fwk
4
 *
5
 * Copyright (c) 2011-2012, Julien Ballestracci <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
15
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
21
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
 * POSSIBILITY OF SUCH DAMAGE.
23
 *
24
 * PHP Version 5.3
25
 *
26
 * @category  Core
27
 * @package   Fwk\Core
28
 * @author    Julien Ballestracci <[email protected]>
29
 * @copyright 2011-2012 Julien Ballestracci <[email protected]>
30
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
31
 * @link      http://www.fwk.pw
32
 */
33
namespace Fwk\Core;
34
35
/**
36
 * This class is a simple accessor for values of objects
37
 *
38
 * If getters/setters are found, we use them first.
39
 * If not, we try to directly get/set the value (\ArrayAccess or \stdClass)
40
 *
41
 * @category Utilities
42
 * @package  Fwk\Core
43
 * @author   Julien Ballestracci <[email protected]>
44
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
45
 * @link     http://www.phpfwk.com
46
 */
47
class Accessor
48
{
49
    /**
50
     * The object to access
51
     *
52
     * @var mixed
53
     */
54
    protected $object;
55
56
    /**
57
     * Reflector for the object
58
     *
59
     * @var \ReflectionObject
60
     */
61
    protected $reflector;
62
63
    /**
64
     * Override properties visibility ?
65
     *
66
     * @var boolean
67
     */
68
    protected $force = false;
69
70
    /**
71
     * Constructor
72
     *
73
     * @param mixed $object The object we want to access
74
     *
75
     * @throws \InvalidArgumentException if $object is not an object
76
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
77
     */
78 17
    public function __construct($object)
79
    {
80 17
        if (!is_object($object)) {
81 1
            throw new \InvalidArgumentException("Argument is not an object");
82
        }
83
84 17
        $this->object   = $object;
85 17
    }
86
87
    /**
88
     * Try to retrieve a value from the object
89
     *
90
     * @param string $key Propertie's name
91
     *
92
     * @return mixed Actual value if reached or false
93
     */
94 10
    public function get($key)
95
    {
96 10
        $obj        = $this->object;
97 10
        $getter     = "get". ucfirst($key);
98
99 10
        if (\method_exists($obj, $getter) && \is_callable(array($obj, $getter))) {
100 7
            return $obj->{$getter}();
101 10
        } elseif ($obj instanceof \stdClass && isset($obj->{$key})) {
102 1
            return  $obj->{$key};
103 10
        } elseif ($obj instanceof \ArrayAccess && $obj->offsetExists($key)) {
104 1
            return  $obj->offsetGet($key);
105
        } else {
106 9
            $this->getReflector();
107
            try {
108 9
                $props   = $this->getClassProperties($this->reflector->getName());
109 9
                if (!isset($props[$key])) {
110 1
                    return false;
111
                }
112 8
                $prop    = $props[$key];
113 8
                if (($prop->isPrivate() || $prop->isProtected()) && $this->force) {
114 3
                    $prop->setAccessible(true);
115 3
                }
116
117 8
                return $prop->getValue($obj);
118 2
            } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
119
            }
120
        }
121
122 2
        return false;
123
    }
124
125
    /**
126
     * Try to set a value
127
     *
128
     * @param string $key   Propertie's name
129
     * @param mixed  $value Desired value
130
     *
131
     * @return boolean true if successful
132
     */
133 7
    public function set($key, $value)
0 ignored issues
show
Coding Style introduced by
function set() does not seem to conform to the naming convention (^(?:is|has|should|may|su...ster|unregister|exists)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
134
    {
135 7
        $obj        = $this->object;
136 7
        $setter     = "set". ucfirst($key);
137
138 7
        if (\method_exists($obj, $setter) && \is_callable(array($obj, $setter))) {
139 3
            $obj->{$setter}($value);
140 3
            return true;
141
        }
142
143 7
        if ($obj instanceof \stdClass) {
144 1
            $obj->{$key}    = $value;
145 1
            return true;
146
        }
147
148 6
        if ($obj instanceof \ArrayAccess) {
149 1
            $obj->offsetSet($key, $value);
150 1
            return true;
151
        }
152
153 5
        $reflector  = $this->getReflector();
154
        try {
155 5
            $prop   = $reflector->getProperty($key);
156
157 5
            if (($prop->isPrivate() || $prop->isProtected()) && $this->force) {
158 1
                $prop->setAccessible(true);
159 1
            }
160
161 5
            if ($prop->isPublic() || $this->force === true) {
162 5
                $prop->setValue($obj, $value);
163
164 5
                return true;
165
            }
166 1
        } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
167
        }
168
169 1
        return false;
170
    }
171
172
    /**
173
     * Set multiple values at once
174
     *
175
     * @param array $values Array of keys->values to be set
176
     *
177
     * @return void
178
     */
179 2
    public function setValues(array $values)
180
    {
181 2
        foreach ($values as $key => $value) {
182 2
            $this->set($key, $value);
183 2
        }
184 2
    }
185
186
    /**
187
     * Gets a reflector for the object
188
     *
189
     * @return \ReflectionObject
190
     */
191 14
    public function getReflector()
192
    {
193 14
        if (!isset($this->reflector)) {
194 14
            $this->reflector = new \ReflectionObject($this->object);
195 14
        }
196
197 14
        return $this->reflector;
198
    }
199
200
    /**
201
     * Make an array of keys->values (eventually filtered by $modifier) from
202
     * object's properties.
203
     *
204
     * @param mixed $modifier Filtering callable
205
     *
206
     * @return array The resulting array
207
     */
208 8
    public function toArray($modifier = null)
209
    {
210 8
        $this->getReflector();
211 8
        $final      = array();
212 8
        $props      = $this->getClassProperties($this->reflector->getName());
213
214 8
        foreach ($props as $property) {
215 6
            $value = $this->get($property->getName());
216
217 6
            if (\is_callable($modifier)) {
218 1
                $value  = \call_user_func_array($modifier, array($value));
219 1
            }
220
221 6
            $final[$property->getName()] = $value;
222 8
        }
223
224 8
        return $final;
225
    }
226
    
227
    /**
228
     * Recursive function to get an associative array of class properties by 
229
     * property name => ReflectionProperty() object including inherited ones 
230
     * from extended classes.
231
     * 
232
     * @param string  $className Class name
233
     * @param integer $filter    ReflectionProperty filter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $filter not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
234
     * 
235
     * @author muratyaman at gmail dot com
236
     * @return array
237
     */
238 11
    protected function getClassProperties($className, $filter = null)
239
    {
240 11
        $ref            = new \ReflectionClass($className);
241 11
        if (null === $filter) {
242
            $filter = \ReflectionProperty::IS_PUBLIC 
243 11
                    | \ReflectionProperty::IS_PRIVATE 
244 11
                    | \ReflectionProperty::IS_PROTECTED 
245 11
                    | \ReflectionProperty::IS_STATIC;
246 11
        }
247
        
248 11
        $props          = $ref->getProperties($filter);
249 11
        $props_arr      = array();
0 ignored issues
show
Coding Style introduced by
$props_arr does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
250 11
        $parentClass    = $ref->getParentClass();
251
        
252 11
        foreach ($props as $prop) {
253 8
            $f              = $prop->getName();
254 8
            $props_arr[$f]  = $prop;
0 ignored issues
show
Coding Style introduced by
$props_arr does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
255 11
        }
256
        
257 11
        if ($parentClass) {
258 5
            $props_arr = array_merge(
0 ignored issues
show
Coding Style introduced by
$props_arr does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
259 5
                $this->getClassProperties($parentClass->getName(), $filter), 
260
                $props_arr
0 ignored issues
show
Coding Style introduced by
$props_arr does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
261 5
            );
262 5
        }
263
        
264 11
        return $props_arr;
0 ignored issues
show
Coding Style introduced by
$props_arr does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
265
    } 
266
267
    /**
268
     * Returns class attributes
269
     *
270
     * @return array The resulting array
271
     */
272 7
    public function getAttributes()
273
    {
274 7
        $reflector  = $this->getReflector();
275 7
        $final      = array();
276
277 7
        foreach ($reflector->getProperties() as $property) {
278 5
            $final[] = $property->getName();
279 7
        }
280
281 7
        return $final;
282
    }
283
284
    /**
285
     * Produces a unique hash code based on values
286
     *
287
     * @param string $algo Desired algorythm
288
     *
289
     * @return string
290
     */
291 1
    public function hashCode($algo  = 'md5')
292
    {
293 1
        $old    = $this->force;
294 1
        $this->overrideVisibility(true);
295 1
        $array  = $this->toArray(array($this, 'everythingAsArrayModifier'));
296 1
        \ksort($array);
297 1
        $str    = \get_class($this->object);
298
299 1
        foreach ($array as $value) {
300 1
            $str .= (is_array($value) ? json_encode($value) : $value);
301 1
        }
302
303 1
        $this->overrideVisibility($old);
304
305 1
        return \hash($algo, $str);
306
    }
307
308
    /**
309
     * Static toArray() modifier to handle objects and relations.
310
     * {@see Accessor::toArray()}
311
     *
312
     * @param mixed $value Actual value
313
     *
314
     * @return mixed Filtered value
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|double|string|null|boolean|resource|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
315
     */
316 1
    public function everythingAsArrayModifier($value)
317
    {
318 1
        if (is_object($value)) {
319 1
            $accessor   = self::factory($value);
320 1
            $value      = $accessor->toArray(array($this, __METHOD__));
321 1
        }
322
323 1
        if (is_array($value)) {
324 1
            foreach ($value as $key => $val) {
325 1
                $value[$key] = $val;
326 1
            }
327 1
        }
328
329 1
        return $value;
330
    }
331
332
    /**
333
     * Factory utility
334
     *
335
     * @param mixed $object The object we want to access
336
     *
337
     * @return Accessor
338
     */
339 2
    public static function factory($object)
340
    {
341 2
        return new static($object);
342
    }
343
344
    /**
345
     * Should we force properties visibility ?
346
     *
347
     * @param boolean $bool yes or no
348
     *
349
     * @return void
350
     */
351 4
    public function overrideVisibility($bool)
352
    {
353 4
        $this->force = (bool) $bool;
354
    }
355
}