http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 21 LOC: 454 Statements: 178
Legend: executednot executeddead code
Source file Statements Methods Total coverage
Type.php 97.8% 81.0% 96.0%
   
1
<?php
2
/**
3
 * Xyster Framework
4
 *
5
 * This source file is subject to the new BSD license that is bundled
6
 * with this package in the file LICENSE.txt.
7
 * It is also available through the world-wide-web at this URL:
8
 * http://www.opensource.org/licenses/bsd-license.php
9
 *
10
 * @category  Xyster
11
 * @package   Xyster_Type
12
 * @copyright Copyright LibreWorks, LLC (http://libreworks.net)
13
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
14
 * @version   $Id: Type.php 418 2010-10-18 21:40:08Z jonathanhawk $
15
 */
16
namespace Xyster\Type;
17
/**
18
 * Type wrapper class
19
 *
20
 * @category  Xyster
21
 * @package   Xyster_Type
22
 * @copyright Copyright LibreWorks, LLC (http://libreworks.net)
23
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
24
 */
25
class Type
26
{
27
    private static $_nesting = 10;
28
    private static $_types = array('array', 'scalar', 'boolean', 'integer',
29
        'double', 'string');
30
    private static $_scalarTypes = array('boolean', 'integer', 'double',
31
        'string');
32
    private static $_classes = array();
33
    private static $_scalarTypeInstances = array();
34
    protected $_type;
35
    /**
36
     * @var ReflectionClass
37
     */
38
    protected $_class;
39
40
    const INT_MAX_32 = 2147483647;
41
    const BOOLEAN = 'boolean';
42
    const DOUBLE = 'double';
43
    const INTEGER = 'integer';
44
    const STRING = 'string';
45
46
    /**
47
     * Creates a new type representation
48
     *
49
     * @param string $type
50
     * @throws InvalidTypeException if the type provided doesn't exist
51
     */
52
    public function __construct($type)
53
    {
54 101
        if ($type instanceof \ReflectionClass) {
55 22
            $type = '\\' . $type->getName();
56 22
        }
57 101
        if (!in_array($type, self::$_types) && !class_exists($type)
58 101
                && !interface_exists($type)) {
59 1
            throw new InvalidTypeException('Invalid type: ' . $type);
60
        }
61 101
        $this->_type = ltrim($type, '\\');
62 101
        if (class_exists($type, false) || interface_exists($type, false)) {
63 100
            $this->_class = self::_getReflectionClass($type);
64 100
        }
65
    }
66
67
    /**
68
     * Tests another value for equality
69
     *
70
     * @param mixed $object
71
     * @return boolean
72
     */
73
    public function equals($object)
74
    {
75 7
        return $object === $this ||
76 7
        ($object instanceof Type && $object->_type == $this->_type);
77
    }
78
79
    /**
80
     * If this type is a class this returns the ReflectionClass object for it
81
     *
82
     * @return ReflectionClass
83
     */
84
    public function getClass()
85
    {
86 82
        return $this->_class;
87
    }
88
89
    /**
90
     * Gets the name of this type
91
     *
92
     * @return string
93
     */
94
    public function getName()
95
    {
96 53
        return $this->_type;
97
    }
98
99
    /**
100
     * Gets a hash code for this type
101
     *
102
     * @return int
103
     */
104
    public function hashCode()
105
    {
106 1
        return self::hash($this->_type);
107
    }
108
109
    /**
110
     * Determines if the supplied type is a child of the current type
111
     *
112
     * The $type argument can be either a string, a ReflectionClass object or a
113
     * Xyster_Type object
114
     *
115
     * @param mixed $type
116
     */
117
    public function isAssignableFrom($type)
118
    {
119 20
        $name = null;
120 20
        $class = null;
121 20
        if (is_string($type)) {
122 8
            $name = ltrim($type, '\\');
123 8
            if (class_exists($type, false)) {
124 7
                $class = new \ReflectionClass($type);
125 7
            }
126 20
        } else if ($type instanceof \ReflectionClass) {
127 1
            $name = $type->getName();
128 1
            $class = $type;
129 18
        } else if ($type instanceof Type) {
130 18
            $name = $type->getName();
131 18
            $class = $type->getClass();
132 18
        }
133
134
        /* @var $class ReflectionClass */
135 20
        return $this->_type == $name ||
136 17
        ($this->_type == 'scalar' && in_array($type, self::$_scalarTypes)) ||
137 20
        ($class && $this->_class && $class->isSubclassOf($this->_class));
138
    }
139
140
    /**
141
     * Determines if the value supplied is an instance of this type
142
     *
143
     * @param mixed $value
144
     * @return boolean
145
     */
146
    public function isInstance($value)
147
    {
148 38
        return $value !== null && (( $this->_class && is_object($value) &&
149 28
        $this->_class->isInstance($value) ) ||
150 38
        ( $this->isAssignableFrom(self::of($value)) ));
151
    }
152
153
    /**
154
     * Gets whether this type is an object type
155
     *
156
     * @return boolean
157
     */
158
    public function isObject()
159
    {
160 25
        return $this->_class instanceof \ReflectionClass;
161
    }
162
163
    /**
164
     * Gets the string representation of this object
165
     *
166
     * @return string
167
     */
168
    public function __toString()
169
    {
170 7
        return (( $this->isObject() ) ? 'Class ' : '') . $this->_type;
171
    }
172
173
    /**
174
     * Checks for shallow equality
175
     *
176
     * If the object you supply for $arg1 has a method named 'equals', that
177
     * method will be called using $arg2 for the argument.
178
     *
179
     * @param mixed $arg1
180
     * @param mixed $arg2
181
     * @return boolean
182
     */
183
    public static function areEqual($arg1, $arg2)
184
    {
185 1
        if ($arg1 === $arg2) {
186 1
            return true;
187
        }
188
189 1
        if (is_numeric($arg1) XOR is_numeric($arg2)) {
190 1
            return false;
191
        }
192 1
        if (is_array($arg1) XOR is_array($arg2)) {
193 1
            return false;
194
        }
195 1
        if (is_object($arg1) XOR is_object($arg2)) {
196 1
            return false;
197
        }
198 1
        if (is_object($arg1) && is_object($arg2) &&
199 1
                get_class($arg1) !== get_class($arg2)) {
200 1
            return false;
201
        }
202
203 1
        if (is_scalar($arg1) || is_scalar($arg2)) {
204 1
            return $arg1 == $arg2;
205
        }
206
207 1
        if (is_object($arg1) && method_exists($arg1, 'equals')) {
208 1
            return $arg1->equals($arg2);
209
        }
210
211 1
        return false;
212
    }
213
214
    /**
215
     * Compares two values for equality
216
     *
217
     * If two objects are not identical (that is, are exactly the same instance)
218
     * they will be compared property-by-property recursively if need be.  This
219
     * method will not nest any deeper than 10 levels to compare objects.
220
     *
221
     * Arrays will be compared in the same manner.
222
     *
223
     * If the object you supply for $arg1 has a method named 'equals', that
224
     * method will be called using $arg2 for the argument.
225
     *
226
     * @param mixed $arg1
227
     * @param mixed $arg2
228
     * @return boolean
229
     */
230
    public static function areDeeplyEqual($arg1, $arg2)
231
    {
232 5
        return self::_areEqual($arg1, $arg2);
233
    }
234
235
    /**
236
     * Gets the types of the parameters for a function or method
237
     *
238
     * @param ReflectionFunctionAbstract $function
239
     * @return \Xyster\Type\Type[]
240
     */
241
    public static function getForParameters(\ReflectionFunctionAbstract $function)
242
    {
243 23
        $types = array();
244 23
        foreach ($function->getParameters() as $parameter) {
245
            /* @var $parameter ReflectionParameter */
246 23
            if ($parameter->isArray()) {
247 10
                $types[] = new self('array');
248 23
            } else if ($parameter->getClass()) {
249 22
                $types[] = new self($parameter->getClass());
250 23
            } else if ($parameter->isDefaultValueAvailable() && !is_null($parameter->getDefaultValue())) {
251 4
                $types[] = self::of($parameter->getDefaultValue());
252 4
            } else {
253 2
                $types[] = new self('scalar');
254
            }
255 23
        }
256 23
        return $types;
257
    }
258
259
    /**
260
     * Gets the type of the value supplied
261
     *
262
     * @param mixed $value
263
     * @return \Xyster\Type\Type
264
     */
265
    public static function of($value)
266
    {
267 12
        $type = is_object($value) ? get_class($value) : gettype($value);
268 12
        return in_array($type, self::$_scalarTypes) ?
269 12
                self::_staticType($type) : new self($type);
270
    }
271
272
    /**
273
     * Gets the Java-style hash code of a value
274
     *
275
     * If you supply an object for the argument and it has a method named
276
     * 'hashCode', that method will be called.
277
     *
278
     * @param mixed $value
279
     * @return int
280
     */
281
    public static function hash($value)
282
    {
283 37
        $hash = 0;
284
285 37
        if (is_string($value)) {
286
287 2
            $max = (float) self::INT_MAX_32;
288 2
            $min = (float) (~self::INT_MAX_32 + 1);
289 2
            $h = 0.0;
290
            // mmm... modular arithmetic...
291 2
            for ($i = 0; $i < strlen($value); $i++) {
292 2
                $result = 31 * $h + ord($value[$i]);
293 2
                if ($result > $max) {
294 2
                    $h = $result >> 32;
295 2
                } else if ($result < $min) {
296 2
                    $h = ~(($result * -1) >> 32) + 1;
297 2
                } else {
298 2
                    $h = $result;
299
                }
300 2
            }
301 2
            $hash = (int) $h;
302 37
        } else if (is_bool($value)) {
303 1
            $hash = $value ? 1231 : 1237;
304 36
        } else if (is_float($value)) {
305 1
            $res = unpack('l', pack('f', $value));
306 1
            $hash = $res[1];
307 36
        } else if (is_int($value)) {
308 36
            $hash = $value;
309 36
        } else if (is_object($value) || is_array($value)) {
310
311 36
            if (is_object($value)) {
312 36
                if (method_exists($value, 'hashCode')) {
313 1
                    return (int) $value->hashCode();
314
                }
315
316 36
                $hex = str_split(spl_object_hash($value), 2);
317 36
                $value = array_map('hexdec', $hex);
318 36
            }
319
320 36
            $max = (float) self::INT_MAX_32;
321 36
            $min = (float) (~self::INT_MAX_32 + 1);
322 36
            $h = 0.0;
323 36
            foreach ($value as $v) {
324 36
                $result = 31 * $h + self::hash($v);
325 36
                if ($result > $max) {
326 36
                    $h = $result >> 32;
327 36
                } else if ($result < $min) {
328 36
                    $h = ~(($result * -1) >> 32) + 1;
329 36
                } else {
330 36
                    $h = $result;
331
                }
332 36
            }
333 36
            $hash = $h;
334 36
        }
335
336 37
        return $hash;
337
    }
338
339
    /**
340
     * Gets a type for the PHP boolean scalar value
341
     *
342
     * @return Xyster_Type
343
     */
344
    public static function boolean()
345
    {
346
        return self::_staticType(self::BOOLEAN);
347
    }
348
349
    /**
350
     * Gets a type for the PHP double scalar value
351
     *
352
     * @return Xyster_Type
353
     */
354
    public static function double()
355
    {
356
        return self::_staticType(self::DOUBLE);
357
    }
358
359
    /**
360
     * Gets a type for the PHP integer scalar value
361
     *
362
     * @return Xyster_Type
363
     */
364
    public static function integer()
365
    {
366
        return self::_staticType(self::INTEGER);
367
    }
368
369
    /**
370
     * Gets a type for the PHP string scalar value
371
     *
372
     * @return Xyster_Type
373
     */
374
    public static function string()
375
    {
376
        return self::_staticType(self::STRING);
377
    }
378
379
    private static function _staticType($name)
380
    {
381 7
        if (!array_key_exists($name, self::$_scalarTypeInstances)) {
382 1
            self::$_scalarTypeInstances[$name] = new Type($name);
383 1
        }
384 7
        return self::$_scalarTypeInstances[$name];
385
    }
386
387
    /**
388
     * Compares two values for equality
389
     *
390
     * @param mixed $arg1
391
     * @param mixed $arg2
392
     * @param int $depth  The current search depth
393
     * @return boolean
394
     */
395
    private static function _areEqual($arg1, $arg2, $depth = 0)
396
    {
397 5
        if ($arg1 === $arg2) {
398 3
            return true;
399
        }
400 5
        if ($depth >= self::$_nesting) {
401 1
            return true;
402
        }
403 5
        if (is_numeric($arg1) XOR is_numeric($arg2)) {
404 1
            return false;
405
        }
406 5
        if (is_array($arg1) XOR is_array($arg2)) {
407 1
            return false;
408
        }
409 5
        if (is_object($arg1) XOR is_object($arg2)) {
410 1
            return false;
411
        }
412 5
        if (is_object($arg1) && is_object($arg2) &&
413 5
                get_class($arg1) !== get_class($arg2)) {
414 3
            return false;
415
        }
416 3
        if (is_scalar($arg1) || is_scalar($arg2)) {
417 1
            return $arg1 == $arg2;
418
        }
419 3
        if (is_object($arg1) && method_exists($arg1, 'equals')) {
420 1
            return $arg1->equals($arg2);
421
        }
422 3
        if (is_object($arg1)) {
423 3
            $arg1 = (array) $arg1;
424 3
            $arg2 = (array) $arg2;
425 3
        }
426 3
        foreach ($arg1 as $key => $v) {
427 3
            if (!array_key_exists($key, $arg2)) {
428 1
                return false;
429
            }
430 3
            if (!self::_areEqual($arg1[$key], $arg2[$key], $depth + 1)) {
431 1
                return false;
432
            }
433 3
            unset($arg2[$key]);
434 3
        }
435 3
        if (count($arg2)) {
436 1
            return false;
437
        }
438 3
        return true;
439
    }
440
441
    /**
442
     * Gets the ReflectionClass for a class name and stores it
443
     *
444
     * @param string $name
445
     * @return ReflectionClass
446
     */
447
    protected static function _getReflectionClass($name)
448
    {
449 100
        if (!isset(self::$_classes[$name])) {
450 25
            self::$_classes[$name] = new \ReflectionClass($name);
451 25
        }
452 100
        return self::$_classes[$name];
453
    }
454 1
}


Report generated at 2010-10-18T17:19:48-04:00