<?php /** * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester * @version $Id: dumper.php 1909 2009-07-29 15:58:11Z dgheath $ */ /** * does type matter */ if (! defined('TYPE_MATTERS')) { define('TYPE_MATTERS', true); } /** * Displays variables as text and does diffs. * @package SimpleTest * @subpackage UnitTester */ class SimpleDumper { /** * Renders a variable in a shorter form than print_r(). * @param mixed $value Variable to render as a string. * @return string Human readable string form. * @access public */ function describeValue($value) { $type = $this->getType($value); switch($type) { case "Null": return "NULL"; case "Boolean": return "Boolean: " . ($value ? "true" : "false"); case "Array": return "Array: " . count($value) . " items"; case "Object": return "Object: of " . get_class($value); case "String": return "String: " . $this->clipString($value, 200); default: return "$type: $value"; } return "Unknown"; } /** * Gets the string representation of a type. * @param mixed $value Variable to check against. * @return string Type. * @access public */ function getType($value) { if (! isset($value)) { return "Null"; } elseif (is_bool($value)) { return "Boolean"; } elseif (is_string($value)) { return "String"; } elseif (is_integer($value)) { return "Integer"; } elseif (is_float($value)) { return "Float"; } elseif (is_array($value)) { return "Array"; } elseif (is_resource($value)) { return "Resource"; } elseif (is_object($value)) { return "Object"; } return "Unknown"; } /** * Creates a human readable description of the * difference between two variables. Uses a * dynamic call. * @param mixed $first First variable. * @param mixed $second Value to compare with. * @param boolean $identical If true then type anomolies count. * @return string Description of difference. * @access public */ function describeDifference($first, $second, $identical = false) { if ($identical) { if (! $this->isTypeMatch($first, $second)) { return "with type mismatch as [" . $this->describeValue($first) . "] does not match [" . $this->describeValue($second) . "]"; } } $type = $this->getType($first); if ($type == "Unknown") { return "with unknown type"; } $method = 'describe' . $type . 'Difference'; return $this->$method($first, $second, $identical); } /** * Tests to see if types match. * @param mixed $first First variable. * @param mixed $second Value to compare with. * @return boolean True if matches. * @access private */ protected function isTypeMatch($first, $second) { return ($this->getType($first) == $this->getType($second)); } /** * Clips a string to a maximum length. * @param string $value String to truncate. * @param integer $size Minimum string size to show. * @param integer $position Centre of string section. * @return string Shortened version. * @access public */ function clipString($value, $size, $position = 0) { $length = strlen($value); if ($length <= $size) { return $value; } $position = min($position, $length); $start = ($size/2 > $position ? 0 : $position - $size/2); if ($start + $size > $length) { $start = $length - $size; } $value = substr($value, $start, $size); return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); } /** * Creates a human readable description of the * difference between two variables. The minimal * version. * @param null $first First value. * @param mixed $second Value to compare with. * @return string Human readable description. * @access private */ protected function describeGenericDifference($first, $second) { return "as [" . $this->describeValue($first) . "] does not match [" . $this->describeValue($second) . "]"; } /** * Creates a human readable description of the * difference between a null and another variable. * @param null $first First null. * @param mixed $second Null to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeNullDifference($first, $second, $identical) { return $this->describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between a boolean and another variable. * @param boolean $first First boolean. * @param mixed $second Boolean to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeBooleanDifference($first, $second, $identical) { return $this->describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between a string and another variable. * @param string $first First string. * @param mixed $second String to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeStringDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->describeGenericDifference($first, $second); } $position = $this->stringDiffersAt($first, $second); $message = "at character $position"; $message .= " with [" . $this->clipString($first, 200, $position) . "] and [" . $this->clipString($second, 200, $position) . "]"; return $message; } /** * Creates a human readable description of the * difference between an integer and another variable. * @param integer $first First number. * @param mixed $second Number to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeIntegerDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->describeGenericDifference($first, $second); } return "because [" . $this->describeValue($first) . "] differs from [" . $this->describeValue($second) . "] by " . abs($first - $second); } /** * Creates a human readable description of the * difference between two floating point numbers. * @param float $first First float. * @param mixed $second Float to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeFloatDifference($first, $second, $identical) { if (is_object($second) || is_array($second)) { return $this->describeGenericDifference($first, $second); } return "because [" . $this->describeValue($first) . "] differs from [" . $this->describeValue($second) . "] by " . abs($first - $second); } /** * Creates a human readable description of the * difference between two arrays. * @param array $first First array. * @param mixed $second Array to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeArrayDifference($first, $second, $identical) { if (! is_array($second)) { return $this->describeGenericDifference($first, $second); } if (! $this->isMatchingKeys($first, $second, $identical)) { return "as key list [" . implode(", ", array_keys($first)) . "] does not match key list [" . implode(", ", array_keys($second)) . "]"; } foreach (array_keys($first) as $key) { if ($identical && ($first[$key] === $second[$key])) { continue; } if (! $identical && ($first[$key] == $second[$key])) { continue; } return "with member [$key] " . $this->describeDifference( $first[$key], $second[$key], $identical); } return ""; } /** * Compares two arrays to see if their key lists match. * For an identical match, the ordering and types of the keys * is significant. * @param array $first First array. * @param array $second Array to compare with. * @param boolean $identical If true then type anomolies count. * @return boolean True if matching. * @access private */ protected function isMatchingKeys($first, $second, $identical) { $first_keys = array_keys($first); $second_keys = array_keys($second); if ($identical) { return ($first_keys === $second_keys); } sort($first_keys); sort($second_keys); return ($first_keys == $second_keys); } /** * Creates a human readable description of the * difference between a resource and another variable. * @param resource $first First resource. * @param mixed $second Resource to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. * @access private */ protected function describeResourceDifference($first, $second, $identical) { return $this->describeGenericDifference($first, $second); } /** * Creates a human readable description of the * difference between two objects. * @param object $first First object. * @param mixed $second Object to compare with. * @param boolean $identical If true then type anomolies count. * @return string Human readable description. */ protected function describeObjectDifference($first, $second, $identical) { if (! is_object($second)) { return $this->describeGenericDifference($first, $second); } return $this->describeArrayDifference( $this->getMembers($first), $this->getMembers($second), $identical); } /** * Get all members of an object including private and protected ones. * A safer form of casting to an array. * @param object $object Object to list members of, * including private ones. * @return array Names and values in the object. */ protected function getMembers($object) { $reflection = new ReflectionObject($object); $members = array(); foreach ($reflection->getProperties() as $property) { if (method_exists($property, 'setAccessible')) { $property->setAccessible(true); } try { $members[$property->getName()] = $property->getValue($object); } catch (ReflectionException $e) { $members[$property->getName()] = $this->getPrivatePropertyNoMatterWhat($property->getName(), $object); } } return $members; } /** * Extracts a private member's value when reflection won't play ball. * @param string $name Property name. * @param object $object Object to read. * @return mixed Value of property. */ private function getPrivatePropertyNoMatterWhat($name, $object) { foreach ((array)$object as $mangled_name => $value) { if ($this->unmangle($mangled_name) == $name) { return $value; } } } /** * Removes crud from property name after it's been converted * to an array. * @param string $mangled Name from array cast. * @return string Cleaned up name. */ function unmangle($mangled) { $parts = preg_split('/[^a-zA-Z0-9_\x7f-\xff]+/', $mangled); return array_pop($parts); } /** * Find the first character position that differs * in two strings by binary chop. * @param string $first First string. * @param string $second String to compare with. * @return integer Position of first differing * character. * @access private */ protected function stringDiffersAt($first, $second) { if (! $first || ! $second) { return 0; } if (strlen($first) < strlen($second)) { list($first, $second) = array($second, $first); } $position = 0; $step = strlen($first); while ($step > 1) { $step = (integer)(($step + 1) / 2); if (strncmp($first, $second, $position + $step) == 0) { $position += $step; } } return $position; } /** * Sends a formatted dump of a variable to a string. * @param mixed $variable Variable to display. * @return string Output from print_r(). * @access public */ function dump($variable) { ob_start(); print_r($variable); $formatted = ob_get_contents(); ob_end_clean(); return $formatted; } } ?>