Exporter.php (8795B)
1 <?php 2 /* 3 * This file is part of the Exporter package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11 namespace SebastianBergmann\Exporter; 12 13 use SebastianBergmann\RecursionContext\Context; 14 15 /** 16 * A nifty utility for visualizing PHP variables. 17 * 18 * <code> 19 * <?php 20 * use SebastianBergmann\Exporter\Exporter; 21 * 22 * $exporter = new Exporter; 23 * print $exporter->export(new Exception); 24 * </code> 25 */ 26 class Exporter 27 { 28 /** 29 * Exports a value as a string 30 * 31 * The output of this method is similar to the output of print_r(), but 32 * improved in various aspects: 33 * 34 * - NULL is rendered as "null" (instead of "") 35 * - TRUE is rendered as "true" (instead of "1") 36 * - FALSE is rendered as "false" (instead of "") 37 * - Strings are always quoted with single quotes 38 * - Carriage returns and newlines are normalized to \n 39 * - Recursion and repeated rendering is treated properly 40 * 41 * @param mixed $value 42 * @param int $indentation The indentation level of the 2nd+ line 43 * @return string 44 */ 45 public function export($value, $indentation = 0) 46 { 47 return $this->recursiveExport($value, $indentation); 48 } 49 50 /** 51 * @param mixed $data 52 * @param Context $context 53 * @return string 54 */ 55 public function shortenedRecursiveExport(&$data, Context $context = null) 56 { 57 $result = array(); 58 $exporter = new self(); 59 60 if (!$context) { 61 $context = new Context; 62 } 63 64 $context->add($data); 65 66 foreach ($data as $key => $value) { 67 if (is_array($value)) { 68 if ($context->contains($data[$key]) !== false) { 69 $result[] = '*RECURSION*'; 70 } 71 72 else { 73 $result[] = sprintf( 74 'array(%s)', 75 $this->shortenedRecursiveExport($data[$key], $context) 76 ); 77 } 78 } 79 80 else { 81 $result[] = $exporter->shortenedExport($value); 82 } 83 } 84 85 return implode(', ', $result); 86 } 87 88 /** 89 * Exports a value into a single-line string 90 * 91 * The output of this method is similar to the output of 92 * SebastianBergmann\Exporter\Exporter::export. This method guarantees 93 * thought that the result contains now newlines. 94 * 95 * Newlines are replaced by the visible string '\n'. Contents of arrays 96 * and objects (if any) are replaced by '...'. 97 * 98 * @param mixed $value 99 * @return string 100 * @see SebastianBergmann\Exporter\Exporter::export 101 */ 102 public function shortenedExport($value) 103 { 104 if (is_string($value)) { 105 $string = $this->export($value); 106 107 if (strlen($string) > 40) { 108 $string = substr($string, 0, 30) . '...' . substr($string, -7); 109 } 110 111 return str_replace("\n", '\n', $string); 112 } 113 114 if (is_object($value)) { 115 return sprintf( 116 '%s Object (%s)', 117 get_class($value), 118 count($this->toArray($value)) > 0 ? '...' : '' 119 ); 120 } 121 122 if (is_array($value)) { 123 return sprintf( 124 'Array (%s)', 125 count($value) > 0 ? '...' : '' 126 ); 127 } 128 129 return $this->export($value); 130 } 131 132 /** 133 * Converts an object to an array containing all of its private, protected 134 * and public properties. 135 * 136 * @param mixed $value 137 * @return array 138 */ 139 public function toArray($value) 140 { 141 if (!is_object($value)) { 142 return (array) $value; 143 } 144 145 $array = array(); 146 147 foreach ((array) $value as $key => $val) { 148 // properties are transformed to keys in the following way: 149 // private $property => "\0Classname\0property" 150 // protected $property => "\0*\0property" 151 // public $property => "property" 152 if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { 153 $key = $matches[1]; 154 } 155 156 // See https://github.com/php/php-src/commit/5721132 157 if ($key === "\0gcdata") { 158 continue; 159 } 160 161 $array[$key] = $val; 162 } 163 164 // Some internal classes like SplObjectStorage don't work with the 165 // above (fast) mechanism nor with reflection in Zend. 166 // Format the output similarly to print_r() in this case 167 if ($value instanceof \SplObjectStorage) { 168 // However, the fast method does work in HHVM, and exposes the 169 // internal implementation. Hide it again. 170 if (property_exists('\SplObjectStorage', '__storage')) { 171 unset($array['__storage']); 172 } elseif (property_exists('\SplObjectStorage', 'storage')) { 173 unset($array['storage']); 174 } 175 176 if (property_exists('\SplObjectStorage', '__key')) { 177 unset($array['__key']); 178 } 179 180 foreach ($value as $key => $val) { 181 $array[spl_object_hash($val)] = array( 182 'obj' => $val, 183 'inf' => $value->getInfo(), 184 ); 185 } 186 } 187 188 return $array; 189 } 190 191 /** 192 * Recursive implementation of export 193 * 194 * @param mixed $value The value to export 195 * @param int $indentation The indentation level of the 2nd+ line 196 * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects 197 * @return string 198 * @see SebastianBergmann\Exporter\Exporter::export 199 */ 200 protected function recursiveExport(&$value, $indentation, $processed = null) 201 { 202 if ($value === null) { 203 return 'null'; 204 } 205 206 if ($value === true) { 207 return 'true'; 208 } 209 210 if ($value === false) { 211 return 'false'; 212 } 213 214 if (is_float($value) && floatval(intval($value)) === $value) { 215 return "$value.0"; 216 } 217 218 if (is_resource($value)) { 219 return sprintf( 220 'resource(%d) of type (%s)', 221 $value, 222 get_resource_type($value) 223 ); 224 } 225 226 if (is_string($value)) { 227 // Match for most non printable chars somewhat taking multibyte chars into account 228 if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) { 229 return 'Binary String: 0x' . bin2hex($value); 230 } 231 232 return "'" . 233 str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . 234 "'"; 235 } 236 237 $whitespace = str_repeat(' ', 4 * $indentation); 238 239 if (!$processed) { 240 $processed = new Context; 241 } 242 243 if (is_array($value)) { 244 if (($key = $processed->contains($value)) !== false) { 245 return 'Array &' . $key; 246 } 247 248 $key = $processed->add($value); 249 $values = ''; 250 251 if (count($value) > 0) { 252 foreach ($value as $k => $v) { 253 $values .= sprintf( 254 '%s %s => %s' . "\n", 255 $whitespace, 256 $this->recursiveExport($k, $indentation), 257 $this->recursiveExport($value[$k], $indentation + 1, $processed) 258 ); 259 } 260 261 $values = "\n" . $values . $whitespace; 262 } 263 264 return sprintf('Array &%s (%s)', $key, $values); 265 } 266 267 if (is_object($value)) { 268 $class = get_class($value); 269 270 if ($hash = $processed->contains($value)) { 271 return sprintf('%s Object &%s', $class, $hash); 272 } 273 274 $hash = $processed->add($value); 275 $values = ''; 276 $array = $this->toArray($value); 277 278 if (count($array) > 0) { 279 foreach ($array as $k => $v) { 280 $values .= sprintf( 281 '%s %s => %s' . "\n", 282 $whitespace, 283 $this->recursiveExport($k, $indentation), 284 $this->recursiveExport($v, $indentation + 1, $processed) 285 ); 286 } 287 288 $values = "\n" . $values . $whitespace; 289 } 290 291 return sprintf('%s Object &%s (%s)', $class, $hash, $values); 292 } 293 294 return var_export($value, true); 295 } 296 }