<?php 
/* 
 * This file is part of the Exporter package. 
 * 
 * (c) Sebastian Bergmann <[email protected]> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace SebastianBergmann\Exporter; 
 
use SebastianBergmann\RecursionContext\Context; 
 
/** 
 * 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  mixed  $value 
     * @param  int    $indentation The indentation level of the 2nd+ line 
     * @return string 
     */ 
    public function export($value, $indentation = 0) 
    { 
        return $this->recursiveExport($value, $indentation); 
    } 
 
    /** 
     * @param  mixed   $data 
     * @param  Context $context 
     * @return string 
     */ 
    public function shortenedRecursiveExport(&$data, Context $context = null) 
    { 
        $result   = array(); 
        $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 '...'. 
     * 
     * @param  mixed  $value 
     * @return string 
     * @see    SebastianBergmann\Exporter\Exporter::export 
     */ 
    public function shortenedExport($value) 
    { 
        if (is_string($value)) { 
            $string = $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 str_replace("\n", '\n', $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. 
     * 
     * @param  mixed $value 
     * @return array 
     */ 
    public function toArray($value) 
    { 
        if (!is_object($value)) { 
            return (array) $value; 
        } 
 
        $array = array(); 
 
        foreach ((array) $value as $key => $val) { 
            // 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(.+)$/', $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) { 
            // However, the fast method does work in HHVM, and exposes the 
            // internal implementation. Hide it again. 
            if (property_exists('\SplObjectStorage', '__storage')) { 
                unset($array['__storage']); 
            } elseif (property_exists('\SplObjectStorage', 'storage')) { 
                unset($array['storage']); 
            } 
 
            if (property_exists('\SplObjectStorage', '__key')) { 
                unset($array['__key']); 
            } 
 
            foreach ($value as $key => $val) { 
                $array[spl_object_hash($val)] = array( 
                    '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) && floatval(intval($value)) === $value) { 
            return "$value.0"; 
        } 
 
        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(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $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); 
    } 
} 
 
 |