You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
9.8 KiB
346 lines
9.8 KiB
<?php declare(strict_types=1); |
|
/* |
|
* This file is part of sebastian/exporter. |
|
* |
|
* (c) Sebastian Bergmann <sebastian@phpunit.de> |
|
* |
|
* For the full copyright and license information, please view the LICENSE |
|
* file that was distributed with this source code. |
|
*/ |
|
namespace SebastianBergmann\Exporter; |
|
|
|
use function bin2hex; |
|
use function count; |
|
use function function_exists; |
|
use function get_class; |
|
use function get_resource_type; |
|
use function gettype; |
|
use function implode; |
|
use function ini_get; |
|
use function ini_set; |
|
use function is_array; |
|
use function is_float; |
|
use function is_object; |
|
use function is_resource; |
|
use function is_string; |
|
use function mb_strlen; |
|
use function mb_substr; |
|
use function preg_match; |
|
use function spl_object_hash; |
|
use function sprintf; |
|
use function str_repeat; |
|
use function str_replace; |
|
use function strlen; |
|
use function substr; |
|
use function var_export; |
|
use SebastianBergmann\RecursionContext\Context; |
|
use SplObjectStorage; |
|
|
|
/** |
|
* A nifty utility for visualizing PHP variables. |
|
* |
|
* <code> |
|
* <?php |
|
* use SebastianBergmann\Exporter\Exporter; |
|
* |
|
* $exporter = new Exporter; |
|
* print $exporter->export(new Exception); |
|
* </code> |
|
*/ |
|
class Exporter |
|
{ |
|
/** |
|
* Exports a value as a string. |
|
* |
|
* The output of this method is similar to the output of print_r(), but |
|
* improved in various aspects: |
|
* |
|
* - NULL is rendered as "null" (instead of "") |
|
* - TRUE is rendered as "true" (instead of "1") |
|
* - FALSE is rendered as "false" (instead of "") |
|
* - Strings are always quoted with single quotes |
|
* - Carriage returns and newlines are normalized to \n |
|
* - Recursion and repeated rendering is treated properly |
|
* |
|
* @param int $indentation The indentation level of the 2nd+ line |
|
* |
|
* @return string |
|
*/ |
|
public function export($value, $indentation = 0) |
|
{ |
|
return $this->recursiveExport($value, $indentation); |
|
} |
|
|
|
/** |
|
* @param array<mixed> $data |
|
* @param Context $context |
|
* |
|
* @return string |
|
*/ |
|
public function shortenedRecursiveExport(&$data, ?Context $context = null) |
|
{ |
|
$result = []; |
|
$exporter = new self(); |
|
|
|
if (!$context) { |
|
$context = new Context; |
|
} |
|
|
|
$array = $data; |
|
$context->add($data); |
|
|
|
foreach ($array as $key => $value) { |
|
if (is_array($value)) { |
|
if ($context->contains($data[$key]) !== false) { |
|
$result[] = '*RECURSION*'; |
|
} else { |
|
$result[] = sprintf( |
|
'array(%s)', |
|
$this->shortenedRecursiveExport($data[$key], $context) |
|
); |
|
} |
|
} else { |
|
$result[] = $exporter->shortenedExport($value); |
|
} |
|
} |
|
|
|
return implode(', ', $result); |
|
} |
|
|
|
/** |
|
* Exports a value into a single-line string. |
|
* |
|
* The output of this method is similar to the output of |
|
* SebastianBergmann\Exporter\Exporter::export(). |
|
* |
|
* Newlines are replaced by the visible string '\n'. |
|
* Contents of arrays and objects (if any) are replaced by '...'. |
|
* |
|
* @return string |
|
* |
|
* @see SebastianBergmann\Exporter\Exporter::export |
|
*/ |
|
public function shortenedExport($value) |
|
{ |
|
if (is_string($value)) { |
|
$string = str_replace("\n", '', $this->export($value)); |
|
|
|
if (function_exists('mb_strlen')) { |
|
if (mb_strlen($string) > 40) { |
|
$string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); |
|
} |
|
} else { |
|
if (strlen($string) > 40) { |
|
$string = substr($string, 0, 30) . '...' . substr($string, -7); |
|
} |
|
} |
|
|
|
return $string; |
|
} |
|
|
|
if (is_object($value)) { |
|
return sprintf( |
|
'%s Object (%s)', |
|
get_class($value), |
|
count($this->toArray($value)) > 0 ? '...' : '' |
|
); |
|
} |
|
|
|
if (is_array($value)) { |
|
return sprintf( |
|
'Array (%s)', |
|
count($value) > 0 ? '...' : '' |
|
); |
|
} |
|
|
|
return $this->export($value); |
|
} |
|
|
|
/** |
|
* Converts an object to an array containing all of its private, protected |
|
* and public properties. |
|
* |
|
* @return array |
|
*/ |
|
public function toArray($value) |
|
{ |
|
if (!is_object($value)) { |
|
return (array) $value; |
|
} |
|
|
|
$array = []; |
|
|
|
foreach ((array) $value as $key => $val) { |
|
// Exception traces commonly reference hundreds to thousands of |
|
// objects currently loaded in memory. Including them in the result |
|
// has a severe negative performance impact. |
|
if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) { |
|
continue; |
|
} |
|
|
|
// properties are transformed to keys in the following way: |
|
// private $property => "\0Classname\0property" |
|
// protected $property => "\0*\0property" |
|
// public $property => "property" |
|
if (preg_match('/^\0.+\0(.+)$/', (string) $key, $matches)) { |
|
$key = $matches[1]; |
|
} |
|
|
|
// See https://github.com/php/php-src/commit/5721132 |
|
if ($key === "\0gcdata") { |
|
continue; |
|
} |
|
|
|
$array[$key] = $val; |
|
} |
|
|
|
// Some internal classes like SplObjectStorage don't work with the |
|
// above (fast) mechanism nor with reflection in Zend. |
|
// Format the output similarly to print_r() in this case |
|
if ($value instanceof SplObjectStorage) { |
|
foreach ($value as $key => $val) { |
|
$array[spl_object_hash($val)] = [ |
|
'obj' => $val, |
|
'inf' => $value->getInfo(), |
|
]; |
|
} |
|
} |
|
|
|
return $array; |
|
} |
|
|
|
/** |
|
* Recursive implementation of export. |
|
* |
|
* @param mixed $value The value to export |
|
* @param int $indentation The indentation level of the 2nd+ line |
|
* @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects |
|
* |
|
* @return string |
|
* |
|
* @see SebastianBergmann\Exporter\Exporter::export |
|
*/ |
|
protected function recursiveExport(&$value, $indentation, $processed = null) |
|
{ |
|
if ($value === null) { |
|
return 'null'; |
|
} |
|
|
|
if ($value === true) { |
|
return 'true'; |
|
} |
|
|
|
if ($value === false) { |
|
return 'false'; |
|
} |
|
|
|
if (is_float($value)) { |
|
$precisionBackup = ini_get('precision'); |
|
|
|
ini_set('precision', '-1'); |
|
|
|
try { |
|
$valueStr = (string) $value; |
|
|
|
if ((string) (int) $value === $valueStr) { |
|
return $valueStr . '.0'; |
|
} |
|
|
|
return $valueStr; |
|
} finally { |
|
ini_set('precision', $precisionBackup); |
|
} |
|
} |
|
|
|
if (gettype($value) === 'resource (closed)') { |
|
return 'resource (closed)'; |
|
} |
|
|
|
if (is_resource($value)) { |
|
return sprintf( |
|
'resource(%d) of type (%s)', |
|
$value, |
|
get_resource_type($value) |
|
); |
|
} |
|
|
|
if (is_string($value)) { |
|
// Match for most non printable chars somewhat taking multibyte chars into account |
|
if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { |
|
return 'Binary String: 0x' . bin2hex($value); |
|
} |
|
|
|
return "'" . |
|
str_replace( |
|
'<lf>', |
|
"\n", |
|
str_replace( |
|
["\r\n", "\n\r", "\r", "\n"], |
|
['\r\n<lf>', '\n\r<lf>', '\r<lf>', '\n<lf>'], |
|
$value |
|
) |
|
) . |
|
"'"; |
|
} |
|
|
|
$whitespace = str_repeat(' ', 4 * $indentation); |
|
|
|
if (!$processed) { |
|
$processed = new Context; |
|
} |
|
|
|
if (is_array($value)) { |
|
if (($key = $processed->contains($value)) !== false) { |
|
return 'Array &' . $key; |
|
} |
|
|
|
$array = $value; |
|
$key = $processed->add($value); |
|
$values = ''; |
|
|
|
if (count($array) > 0) { |
|
foreach ($array as $k => $v) { |
|
$values .= sprintf( |
|
'%s %s => %s' . "\n", |
|
$whitespace, |
|
$this->recursiveExport($k, $indentation), |
|
$this->recursiveExport($value[$k], $indentation + 1, $processed) |
|
); |
|
} |
|
|
|
$values = "\n" . $values . $whitespace; |
|
} |
|
|
|
return sprintf('Array &%s (%s)', $key, $values); |
|
} |
|
|
|
if (is_object($value)) { |
|
$class = get_class($value); |
|
|
|
if ($hash = $processed->contains($value)) { |
|
return sprintf('%s Object &%s', $class, $hash); |
|
} |
|
|
|
$hash = $processed->add($value); |
|
$values = ''; |
|
$array = $this->toArray($value); |
|
|
|
if (count($array) > 0) { |
|
foreach ($array as $k => $v) { |
|
$values .= sprintf( |
|
'%s %s => %s' . "\n", |
|
$whitespace, |
|
$this->recursiveExport($k, $indentation), |
|
$this->recursiveExport($v, $indentation + 1, $processed) |
|
); |
|
} |
|
|
|
$values = "\n" . $values . $whitespace; |
|
} |
|
|
|
return sprintf('%s Object &%s (%s)', $class, $hash, $values); |
|
} |
|
|
|
return var_export($value, true); |
|
} |
|
}
|
|
|