1<?php 2/** 3 * base include file for SimpleTest 4 * @package SimpleTest 5 * @subpackage UnitTester 6 * @version $Id$ 7 */ 8/** 9 * does type matter 10 */ 11if (! defined('TYPE_MATTERS')) { 12 define('TYPE_MATTERS', true); 13} 14 15/** 16 * Displays variables as text and does diffs. 17 * @package SimpleTest 18 * @subpackage UnitTester 19 */ 20class SimpleDumper { 21 22 /** 23 * Renders a variable in a shorter form than print_r(). 24 * @param mixed $value Variable to render as a string. 25 * @return string Human readable string form. 26 * @access public 27 */ 28 function describeValue($value) { 29 $type = $this->getType($value); 30 switch($type) { 31 case "Null": 32 return "NULL"; 33 case "Boolean": 34 return "Boolean: " . ($value ? "true" : "false"); 35 case "Array": 36 return "Array: " . count($value) . " items"; 37 case "Object": 38 return "Object: of " . get_class($value); 39 case "String": 40 return "String: " . $this->clipString($value, 200); 41 default: 42 return "$type: $value"; 43 } 44 return "Unknown"; 45 } 46 47 /** 48 * Gets the string representation of a type. 49 * @param mixed $value Variable to check against. 50 * @return string Type. 51 * @access public 52 */ 53 function getType($value) { 54 if (! isset($value)) { 55 return "Null"; 56 } elseif (is_bool($value)) { 57 return "Boolean"; 58 } elseif (is_string($value)) { 59 return "String"; 60 } elseif (is_integer($value)) { 61 return "Integer"; 62 } elseif (is_float($value)) { 63 return "Float"; 64 } elseif (is_array($value)) { 65 return "Array"; 66 } elseif (is_resource($value)) { 67 return "Resource"; 68 } elseif (is_object($value)) { 69 return "Object"; 70 } 71 return "Unknown"; 72 } 73 74 /** 75 * Creates a human readable description of the 76 * difference between two variables. Uses a 77 * dynamic call. 78 * @param mixed $first First variable. 79 * @param mixed $second Value to compare with. 80 * @param boolean $identical If true then type anomolies count. 81 * @return string Description of difference. 82 * @access public 83 */ 84 function describeDifference($first, $second, $identical = false) { 85 if ($identical) { 86 if (! $this->isTypeMatch($first, $second)) { 87 return "with type mismatch as [" . $this->describeValue($first) . 88 "] does not match [" . $this->describeValue($second) . "]"; 89 } 90 } 91 $type = $this->getType($first); 92 if ($type == "Unknown") { 93 return "with unknown type"; 94 } 95 $method = 'describe' . $type . 'Difference'; 96 return $this->$method($first, $second, $identical); 97 } 98 99 /** 100 * Tests to see if types match. 101 * @param mixed $first First variable. 102 * @param mixed $second Value to compare with. 103 * @return boolean True if matches. 104 * @access private 105 */ 106 protected function isTypeMatch($first, $second) { 107 return ($this->getType($first) == $this->getType($second)); 108 } 109 110 /** 111 * Clips a string to a maximum length. 112 * @param string $value String to truncate. 113 * @param integer $size Minimum string size to show. 114 * @param integer $position Centre of string section. 115 * @return string Shortened version. 116 * @access public 117 */ 118 function clipString($value, $size, $position = 0) { 119 $length = strlen($value); 120 if ($length <= $size) { 121 return $value; 122 } 123 $position = min($position, $length); 124 $start = ($size/2 > $position ? 0 : $position - $size/2); 125 if ($start + $size > $length) { 126 $start = $length - $size; 127 } 128 $value = substr($value, $start, $size); 129 return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); 130 } 131 132 /** 133 * Creates a human readable description of the 134 * difference between two variables. The minimal 135 * version. 136 * @param null $first First value. 137 * @param mixed $second Value to compare with. 138 * @return string Human readable description. 139 * @access private 140 */ 141 protected function describeGenericDifference($first, $second) { 142 return "as [" . $this->describeValue($first) . 143 "] does not match [" . 144 $this->describeValue($second) . "]"; 145 } 146 147 /** 148 * Creates a human readable description of the 149 * difference between a null and another variable. 150 * @param null $first First null. 151 * @param mixed $second Null to compare with. 152 * @param boolean $identical If true then type anomolies count. 153 * @return string Human readable description. 154 * @access private 155 */ 156 protected function describeNullDifference($first, $second, $identical) { 157 return $this->describeGenericDifference($first, $second); 158 } 159 160 /** 161 * Creates a human readable description of the 162 * difference between a boolean and another variable. 163 * @param boolean $first First boolean. 164 * @param mixed $second Boolean to compare with. 165 * @param boolean $identical If true then type anomolies count. 166 * @return string Human readable description. 167 * @access private 168 */ 169 protected function describeBooleanDifference($first, $second, $identical) { 170 return $this->describeGenericDifference($first, $second); 171 } 172 173 /** 174 * Creates a human readable description of the 175 * difference between a string and another variable. 176 * @param string $first First string. 177 * @param mixed $second String to compare with. 178 * @param boolean $identical If true then type anomolies count. 179 * @return string Human readable description. 180 * @access private 181 */ 182 protected function describeStringDifference($first, $second, $identical) { 183 if (is_object($second) || is_array($second)) { 184 return $this->describeGenericDifference($first, $second); 185 } 186 $position = $this->stringDiffersAt($first, $second); 187 $message = "at character $position"; 188 $message .= " with [" . 189 $this->clipString($first, 200, $position) . "] and [" . 190 $this->clipString($second, 200, $position) . "]"; 191 return $message; 192 } 193 194 /** 195 * Creates a human readable description of the 196 * difference between an integer and another variable. 197 * @param integer $first First number. 198 * @param mixed $second Number to compare with. 199 * @param boolean $identical If true then type anomolies count. 200 * @return string Human readable description. 201 * @access private 202 */ 203 protected function describeIntegerDifference($first, $second, $identical) { 204 if (is_object($second) || is_array($second)) { 205 return $this->describeGenericDifference($first, $second); 206 } 207 return "because [" . $this->describeValue($first) . 208 "] differs from [" . 209 $this->describeValue($second) . "] by " . 210 abs($first - $second); 211 } 212 213 /** 214 * Creates a human readable description of the 215 * difference between two floating point numbers. 216 * @param float $first First float. 217 * @param mixed $second Float to compare with. 218 * @param boolean $identical If true then type anomolies count. 219 * @return string Human readable description. 220 * @access private 221 */ 222 protected function describeFloatDifference($first, $second, $identical) { 223 if (is_object($second) || is_array($second)) { 224 return $this->describeGenericDifference($first, $second); 225 } 226 return "because [" . $this->describeValue($first) . 227 "] differs from [" . 228 $this->describeValue($second) . "] by " . 229 abs($first - $second); 230 } 231 232 /** 233 * Creates a human readable description of the 234 * difference between two arrays. 235 * @param array $first First array. 236 * @param mixed $second Array to compare with. 237 * @param boolean $identical If true then type anomolies count. 238 * @return string Human readable description. 239 * @access private 240 */ 241 protected function describeArrayDifference($first, $second, $identical) { 242 if (! is_array($second)) { 243 return $this->describeGenericDifference($first, $second); 244 } 245 if (! $this->isMatchingKeys($first, $second, $identical)) { 246 return "as key list [" . 247 implode(", ", array_keys($first)) . "] does not match key list [" . 248 implode(", ", array_keys($second)) . "]"; 249 } 250 foreach (array_keys($first) as $key) { 251 if ($identical && ($first[$key] === $second[$key])) { 252 continue; 253 } 254 if (! $identical && ($first[$key] == $second[$key])) { 255 continue; 256 } 257 return "with member [$key] " . $this->describeDifference( 258 $first[$key], 259 $second[$key], 260 $identical); 261 } 262 return ""; 263 } 264 265 /** 266 * Compares two arrays to see if their key lists match. 267 * For an identical match, the ordering and types of the keys 268 * is significant. 269 * @param array $first First array. 270 * @param array $second Array to compare with. 271 * @param boolean $identical If true then type anomolies count. 272 * @return boolean True if matching. 273 * @access private 274 */ 275 protected function isMatchingKeys($first, $second, $identical) { 276 $first_keys = array_keys($first); 277 $second_keys = array_keys($second); 278 if ($identical) { 279 return ($first_keys === $second_keys); 280 } 281 sort($first_keys); 282 sort($second_keys); 283 return ($first_keys == $second_keys); 284 } 285 286 /** 287 * Creates a human readable description of the 288 * difference between a resource and another variable. 289 * @param resource $first First resource. 290 * @param mixed $second Resource to compare with. 291 * @param boolean $identical If true then type anomolies count. 292 * @return string Human readable description. 293 * @access private 294 */ 295 protected function describeResourceDifference($first, $second, $identical) { 296 return $this->describeGenericDifference($first, $second); 297 } 298 299 /** 300 * Creates a human readable description of the 301 * difference between two objects. 302 * @param object $first First object. 303 * @param mixed $second Object to compare with. 304 * @param boolean $identical If true then type anomolies count. 305 * @return string Human readable description. 306 */ 307 protected function describeObjectDifference($first, $second, $identical) { 308 if (! is_object($second)) { 309 return $this->describeGenericDifference($first, $second); 310 } 311 return $this->describeArrayDifference( 312 $this->getMembers($first), 313 $this->getMembers($second), 314 $identical); 315 } 316 317 /** 318 * Get all members of an object including private and protected ones. 319 * A safer form of casting to an array. 320 * @param object $object Object to list members of, 321 * including private ones. 322 * @return array Names and values in the object. 323 */ 324 protected function getMembers($object) { 325 $reflection = new ReflectionObject($object); 326 $members = array(); 327 foreach ($reflection->getProperties() as $property) { 328 if (method_exists($property, 'setAccessible')) { 329 $property->setAccessible(true); 330 } 331 try { 332 $members[$property->getName()] = $property->getValue($object); 333 } catch (ReflectionException $e) { 334 $members[$property->getName()] = 335 $this->getPrivatePropertyNoMatterWhat($property->getName(), $object); 336 } 337 } 338 return $members; 339 } 340 341 /** 342 * Extracts a private member's value when reflection won't play ball. 343 * @param string $name Property name. 344 * @param object $object Object to read. 345 * @return mixed Value of property. 346 */ 347 private function getPrivatePropertyNoMatterWhat($name, $object) { 348 foreach ((array)$object as $mangled_name => $value) { 349 if ($this->unmangle($mangled_name) == $name) { 350 return $value; 351 } 352 } 353 } 354 355 /** 356 * Removes crud from property name after it's been converted 357 * to an array. 358 * @param string $mangled Name from array cast. 359 * @return string Cleaned up name. 360 */ 361 function unmangle($mangled) { 362 $parts = preg_split('/[^a-zA-Z0-9_\x7f-\xff]+/', $mangled); 363 return array_pop($parts); 364 } 365 366 /** 367 * Find the first character position that differs 368 * in two strings by binary chop. 369 * @param string $first First string. 370 * @param string $second String to compare with. 371 * @return integer Position of first differing 372 * character. 373 * @access private 374 */ 375 protected function stringDiffersAt($first, $second) { 376 if (! $first || ! $second) { 377 return 0; 378 } 379 if (strlen($first) < strlen($second)) { 380 list($first, $second) = array($second, $first); 381 } 382 $position = 0; 383 $step = strlen($first); 384 while ($step > 1) { 385 $step = (integer)(($step + 1) / 2); 386 if (strncmp($first, $second, $position + $step) == 0) { 387 $position += $step; 388 } 389 } 390 return $position; 391 } 392 393 /** 394 * Sends a formatted dump of a variable to a string. 395 * @param mixed $variable Variable to display. 396 * @return string Output from print_r(). 397 * @access public 398 */ 399 function dump($variable) { 400 ob_start(); 401 print_r($variable); 402 $formatted = ob_get_contents(); 403 ob_end_clean(); 404 return $formatted; 405 } 406} 407?>