1<?php 2 3namespace Prophecy\Util; 4 5use Prophecy\Prophecy\ProphecyInterface; 6use SebastianBergmann\RecursionContext\Context; 7 8/* 9 * This file is part of the Prophecy. 10 * (c) Konstantin Kudryashov <ever.zet@gmail.com> 11 * Marcello Duarte <marcello.duarte@gmail.com> 12 * 13 * For the full copyright and license information, please view the LICENSE 14 * file that was distributed with this source code. 15 */ 16 17/** 18 * This class is a modification from sebastianbergmann/exporter 19 * @see https://github.com/sebastianbergmann/exporter 20 */ 21class ExportUtil 22{ 23 /** 24 * Exports a value as a string 25 * 26 * The output of this method is similar to the output of print_r(), but 27 * improved in various aspects: 28 * 29 * - NULL is rendered as "null" (instead of "") 30 * - TRUE is rendered as "true" (instead of "1") 31 * - FALSE is rendered as "false" (instead of "") 32 * - Strings are always quoted with single quotes 33 * - Carriage returns and newlines are normalized to \n 34 * - Recursion and repeated rendering is treated properly 35 * 36 * @param mixed $value 37 * @param int $indentation The indentation level of the 2nd+ line 38 * @return string 39 */ 40 public static function export($value, $indentation = 0) 41 { 42 return self::recursiveExport($value, $indentation); 43 } 44 45 /** 46 * Converts an object to an array containing all of its private, protected 47 * and public properties. 48 * 49 * @param mixed $value 50 * @return array 51 */ 52 public static function toArray($value) 53 { 54 if (!is_object($value)) { 55 return (array) $value; 56 } 57 58 $array = array(); 59 60 foreach ((array) $value as $key => $val) { 61 // properties are transformed to keys in the following way: 62 // private $property => "\0Classname\0property" 63 // protected $property => "\0*\0property" 64 // public $property => "property" 65 if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { 66 $key = $matches[1]; 67 } 68 69 // See https://github.com/php/php-src/commit/5721132 70 if ($key === "\0gcdata") { 71 continue; 72 } 73 74 $array[$key] = $val; 75 } 76 77 // Some internal classes like SplObjectStorage don't work with the 78 // above (fast) mechanism nor with reflection in Zend. 79 // Format the output similarly to print_r() in this case 80 if ($value instanceof \SplObjectStorage) { 81 // However, the fast method does work in HHVM, and exposes the 82 // internal implementation. Hide it again. 83 if (property_exists('\SplObjectStorage', '__storage')) { 84 unset($array['__storage']); 85 } elseif (property_exists('\SplObjectStorage', 'storage')) { 86 unset($array['storage']); 87 } 88 89 if (property_exists('\SplObjectStorage', '__key')) { 90 unset($array['__key']); 91 } 92 93 foreach ($value as $key => $val) { 94 $array[spl_object_hash($val)] = array( 95 'obj' => $val, 96 'inf' => $value->getInfo(), 97 ); 98 } 99 } 100 101 return $array; 102 } 103 104 /** 105 * Recursive implementation of export 106 * 107 * @param mixed $value The value to export 108 * @param int $indentation The indentation level of the 2nd+ line 109 * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects 110 * @return string 111 * @see SebastianBergmann\Exporter\Exporter::export 112 */ 113 protected static function recursiveExport(&$value, $indentation, $processed = null) 114 { 115 if ($value === null) { 116 return 'null'; 117 } 118 119 if ($value === true) { 120 return 'true'; 121 } 122 123 if ($value === false) { 124 return 'false'; 125 } 126 127 if (is_float($value) && floatval(intval($value)) === $value) { 128 return "$value.0"; 129 } 130 131 if (is_resource($value)) { 132 return sprintf( 133 'resource(%d) of type (%s)', 134 $value, 135 get_resource_type($value) 136 ); 137 } 138 139 if (is_string($value)) { 140 // Match for most non printable chars somewhat taking multibyte chars into account 141 if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) { 142 return 'Binary String: 0x' . bin2hex($value); 143 } 144 145 return "'" . 146 str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . 147 "'"; 148 } 149 150 $whitespace = str_repeat(' ', 4 * $indentation); 151 152 if (!$processed) { 153 $processed = new Context; 154 } 155 156 if (is_array($value)) { 157 if (($key = $processed->contains($value)) !== false) { 158 return 'Array &' . $key; 159 } 160 161 $array = $value; 162 $key = $processed->add($value); 163 $values = ''; 164 165 if (count($array) > 0) { 166 foreach ($array as $k => $v) { 167 $values .= sprintf( 168 '%s %s => %s' . "\n", 169 $whitespace, 170 self::recursiveExport($k, $indentation), 171 self::recursiveExport($value[$k], $indentation + 1, $processed) 172 ); 173 } 174 175 $values = "\n" . $values . $whitespace; 176 } 177 178 return sprintf('Array &%s (%s)', $key, $values); 179 } 180 181 if (is_object($value)) { 182 $class = get_class($value); 183 184 if ($value instanceof ProphecyInterface) { 185 return sprintf('%s Object (*Prophecy*)', $class); 186 } elseif ($hash = $processed->contains($value)) { 187 return sprintf('%s:%s Object', $class, $hash); 188 } 189 190 $hash = $processed->add($value); 191 $values = ''; 192 $array = self::toArray($value); 193 194 if (count($array) > 0) { 195 foreach ($array as $k => $v) { 196 $values .= sprintf( 197 '%s %s => %s' . "\n", 198 $whitespace, 199 self::recursiveExport($k, $indentation), 200 self::recursiveExport($v, $indentation + 1, $processed) 201 ); 202 } 203 204 $values = "\n" . $values . $whitespace; 205 } 206 207 return sprintf('%s:%s Object (%s)', $class, $hash, $values); 208 } 209 210 return var_export($value, true); 211 } 212} 213