http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 18 LOC: 428 Statements: 166

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


Report generated at 2008-03-05T18:27:43-05:00