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 11namespace SebastianBergmann\Exporter; 12 13use 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 */ 26class 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 $array = $data; 65 $context->add($data); 66 67 foreach ($array as $key => $value) { 68 if (is_array($value)) { 69 if ($context->contains($data[$key]) !== false) { 70 $result[] = '*RECURSION*'; 71 } 72 73 else { 74 $result[] = sprintf( 75 'array(%s)', 76 $this->shortenedRecursiveExport($data[$key], $context) 77 ); 78 } 79 } 80 81 else { 82 $result[] = $exporter->shortenedExport($value); 83 } 84 } 85 86 return implode(', ', $result); 87 } 88 89 /** 90 * Exports a value into a single-line string 91 * 92 * The output of this method is similar to the output of 93 * SebastianBergmann\Exporter\Exporter::export(). 94 * 95 * Newlines are replaced by the visible string '\n'. 96 * Contents of arrays 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 (function_exists('mb_strlen')) { 108 if (mb_strlen($string) > 40) { 109 $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); 110 } 111 } else { 112 if (strlen($string) > 40) { 113 $string = substr($string, 0, 30) . '...' . substr($string, -7); 114 } 115 } 116 117 return str_replace("\n", '\n', $string); 118 } 119 120 if (is_object($value)) { 121 return sprintf( 122 '%s Object (%s)', 123 get_class($value), 124 count($this->toArray($value)) > 0 ? '...' : '' 125 ); 126 } 127 128 if (is_array($value)) { 129 return sprintf( 130 'Array (%s)', 131 count($value) > 0 ? '...' : '' 132 ); 133 } 134 135 return $this->export($value); 136 } 137 138 /** 139 * Converts an object to an array containing all of its private, protected 140 * and public properties. 141 * 142 * @param mixed $value 143 * @return array 144 */ 145 public function toArray($value) 146 { 147 if (!is_object($value)) { 148 return (array) $value; 149 } 150 151 $array = array(); 152 153 foreach ((array) $value as $key => $val) { 154 // properties are transformed to keys in the following way: 155 // private $property => "\0Classname\0property" 156 // protected $property => "\0*\0property" 157 // public $property => "property" 158 if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { 159 $key = $matches[1]; 160 } 161 162 // See https://github.com/php/php-src/commit/5721132 163 if ($key === "\0gcdata") { 164 continue; 165 } 166 167 $array[$key] = $val; 168 } 169 170 // Some internal classes like SplObjectStorage don't work with the 171 // above (fast) mechanism nor with reflection in Zend. 172 // Format the output similarly to print_r() in this case 173 if ($value instanceof \SplObjectStorage) { 174 // However, the fast method does work in HHVM, and exposes the 175 // internal implementation. Hide it again. 176 if (property_exists('\SplObjectStorage', '__storage')) { 177 unset($array['__storage']); 178 } elseif (property_exists('\SplObjectStorage', 'storage')) { 179 unset($array['storage']); 180 } 181 182 if (property_exists('\SplObjectStorage', '__key')) { 183 unset($array['__key']); 184 } 185 186 foreach ($value as $key => $val) { 187 $array[spl_object_hash($val)] = array( 188 'obj' => $val, 189 'inf' => $value->getInfo(), 190 ); 191 } 192 } 193 194 return $array; 195 } 196 197 /** 198 * Recursive implementation of export 199 * 200 * @param mixed $value The value to export 201 * @param int $indentation The indentation level of the 2nd+ line 202 * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects 203 * @return string 204 * @see SebastianBergmann\Exporter\Exporter::export 205 */ 206 protected function recursiveExport(&$value, $indentation, $processed = null) 207 { 208 if ($value === null) { 209 return 'null'; 210 } 211 212 if ($value === true) { 213 return 'true'; 214 } 215 216 if ($value === false) { 217 return 'false'; 218 } 219 220 if (is_float($value) && floatval(intval($value)) === $value) { 221 return "$value.0"; 222 } 223 224 if (is_resource($value)) { 225 return sprintf( 226 'resource(%d) of type (%s)', 227 $value, 228 get_resource_type($value) 229 ); 230 } 231 232 if (is_string($value)) { 233 // Match for most non printable chars somewhat taking multibyte chars into account 234 if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { 235 return 'Binary String: 0x' . bin2hex($value); 236 } 237 238 return "'" . 239 str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . 240 "'"; 241 } 242 243 $whitespace = str_repeat(' ', 4 * $indentation); 244 245 if (!$processed) { 246 $processed = new Context; 247 } 248 249 if (is_array($value)) { 250 if (($key = $processed->contains($value)) !== false) { 251 return 'Array &' . $key; 252 } 253 254 $array = $value; 255 $key = $processed->add($value); 256 $values = ''; 257 258 if (count($array) > 0) { 259 foreach ($array as $k => $v) { 260 $values .= sprintf( 261 '%s %s => %s' . "\n", 262 $whitespace, 263 $this->recursiveExport($k, $indentation), 264 $this->recursiveExport($value[$k], $indentation + 1, $processed) 265 ); 266 } 267 268 $values = "\n" . $values . $whitespace; 269 } 270 271 return sprintf('Array &%s (%s)', $key, $values); 272 } 273 274 if (is_object($value)) { 275 $class = get_class($value); 276 277 if ($hash = $processed->contains($value)) { 278 return sprintf('%s Object &%s', $class, $hash); 279 } 280 281 $hash = $processed->add($value); 282 $values = ''; 283 $array = $this->toArray($value); 284 285 if (count($array) > 0) { 286 foreach ($array as $k => $v) { 287 $values .= sprintf( 288 '%s %s => %s' . "\n", 289 $whitespace, 290 $this->recursiveExport($k, $indentation), 291 $this->recursiveExport($v, $indentation + 1, $processed) 292 ); 293 } 294 295 $values = "\n" . $values . $whitespace; 296 } 297 298 return sprintf('%s Object &%s (%s)', $class, $hash, $values); 299 } 300 301 return var_export($value, true); 302 } 303} 304