dumper.php 15 KB

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