Skip to content

Commit

Permalink
Merge pull request #773 from LawnGnome/recursive-export
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Jan 8, 2013
2 parents 1e5f090 + bf93da1 commit 9267b0b
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 80 deletions.
1 change: 1 addition & 0 deletions PHPUnit/Autoload.php
Expand Up @@ -175,6 +175,7 @@ function ($class)
'phpunit_util_testdox_resultprinter_text' => '/Util/TestDox/ResultPrinter/Text.php',
'phpunit_util_testsuiteiterator' => '/Util/TestSuiteIterator.php',
'phpunit_util_type' => '/Util/Type.php',
'phpunit_util_type_exportcontext' => '/Util/Type/ExportContext.php',
'phpunit_util_xml' => '/Util/XML.php'
);

Expand Down
94 changes: 43 additions & 51 deletions PHPUnit/Util/Type.php
Expand Up @@ -105,13 +105,15 @@ public static function export($value, $indentation = 0)
*
* @param mixed $value The value to export
* @param integer $indentation The indentation level of the 2nd+ line
* @param array $processedObjects Contains all objects that were already
* rendered
* @param PHPUnit_Util_Type_ExportContext $processed Contains all objects
* and arrays that have
* previously been
* rendered
* @return string
* @since Method available since Release 3.6.0
* @see PHPUnit_Util_Type::export
*/
protected static function recursiveExport($value, $indentation, &$processedObjects = array())
protected static function recursiveExport(&$value, $indentation, $processed = null)
{
if ($value === NULL) {
return 'null';
Expand All @@ -125,6 +127,10 @@ protected static function recursiveExport($value, $indentation, &$processedObjec
return 'false';
}

if (is_float($value) && floatval(intval($value)) === $value) {
return "$value.0";
}

if (is_string($value)) {
// Match for most non printable chars somewhat taking multibyte chars into account
if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) {
Expand All @@ -136,71 +142,57 @@ protected static function recursiveExport($value, $indentation, &$processedObjec
"'";
}

$origValue = $value;

if (is_object($value)) {
if (in_array($value, $processedObjects, TRUE)) {
return sprintf(
'%s Object (*RECURSION*)',

get_class($value)
);
}

$processedObjects[] = $value;
$whitespace = str_repeat(' ', 4 * $indentation);

// Convert object to array
$value = self::toArray($value);
if (!$processed) {
$processed = new PHPUnit_Util_Type_ExportContext;
}

if (is_array($value)) {
$whitespace = str_repeat(' ', $indentation);

// There seems to be no other way to check arrays for recursion
// http://www.php.net/manual/en/language.types.array.php#73936
preg_match_all('/\n \[(\w+)\] => Array\s+\*RECURSION\*/', print_r($value, TRUE), $matches);
$recursiveKeys = array_unique($matches[1]);

// Convert to valid array keys
// Numeric integer strings are automatically converted to integers
// by PHP
foreach ($recursiveKeys as $key => $recursiveKey) {
if ((string)(integer)$recursiveKey === $recursiveKey) {
$recursiveKeys[$key] = (integer)$recursiveKey;
}
if (($key = $processed->contains($value)) !== false) {
return "Array &$key";
}

$content = '';

foreach ($value as $key => $val) {
if (in_array($key, $recursiveKeys, TRUE)) {
$val = 'Array (*RECURSION*)';
}
$key = $processed->add($value);
if (count($value) > 0) {
$output = "Array &$key (\n";

else {
$val = self::recursiveExport($val, $indentation+1, $processedObjects);
foreach ($value as $k => $v) {
$k = self::export($k);
$output .= "$whitespace $k => ".self::recursiveExport($v, $indentation + 1, $processed)."\n";
}

$content .= $whitespace . ' ' . self::export($key) . ' => ' . $val . "\n";
return "$output$whitespace)";
} else {
return "Array &$key ()";
}
}

if (is_object($value)) {
$class = get_class($value);

if (strlen($content) > 0) {
$content = "\n" . $content . $whitespace;
if ($hash = $processed->contains($value)) {
return "$class Object &$hash";
}

return sprintf(
"%s (%s)",
$hash = $processed->add($value);
$array = self::toArray($value);
if (count($array) > 0) {
$output = "$class Object &$hash (\n";

is_object($origValue) ? get_class($origValue) . ' Object' : 'Array',
$content
);
}
foreach ($array as $k => $v) {
$k = self::export($k);
$output .= "$whitespace $k => ".self::recursiveExport($v, $indentation + 1, $processed)."\n";
}

return "$output$whitespace)";
} else {
return "$class Object &$hash ()";
}

if (is_double($value) && (double)(integer)$value === $value) {
return $value . '.0';
}

return (string)$value;
return var_export($value, true);
}

/**
Expand Down
189 changes: 189 additions & 0 deletions PHPUnit/Util/Type/ExportContext.php
@@ -0,0 +1,189 @@
<?php
/**
* PHPUnit
*
* Copyright (c) 2001-2013, Sebastian Bergmann <sebastian@phpunit.de>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Sebastian Bergmann nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package PHPUnit
* @subpackage Util
* @author Adam Harvey <aharvey@php.net>
* @copyright 2001-2013 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
* @link http://www.phpunit.de/
* @since File available since Release 3.8.0
*/

/**
* A context containing previously rendered arrays and objects when recursively
* exporting a value.
*
* @package PHPUnit
* @subpackage Util
* @author Adam Harvey <aharvey@php.net>
* @copyright 2001-2013 Sebastian Bergmann <sebastian@phpunit.de>
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
* @link http://www.phpunit.de/
* @since Class available since Release 3.8.0
*/
class PHPUnit_Util_Type_ExportContext {
/**
* Previously seen arrays.
*
* @var array[] $arrays
*/
protected $arrays;

/**
* Previously seen objects.
*
* @var SplObjectStorage $objects
*/
protected $objects;

/** Initialises the context. */
public function __construct()
{
$this->arrays = [];
$this->objects = new SplObjectStorage;
}

/**
* Adds a value to the export context.
*
* @param mixed $value The value to add.
* @return mixed The ID of the stored value, either as a string or integer.
* @throws PHPUnit_Framework_Exception Thrown if $value is not an array or
* object.
*/
public function add(&$value)
{
if (is_array($value)) {
return $this->addArray($value);
} elseif (is_object($value)) {
return $this->addObject($value);
}

throw new PHPUnit_Framework_Exception('Only arrays and objects are supported');
}

/**
* Checks if the given value exists within the context.
*
* @param mixed $value The value to check.
* @return mixed The string or integer ID of the stored value if it has
* already been seen, or boolean false if the value is not
* stored.
* @throws PHPUnit_Framework_Exception Thrown if $value is not an array or
* object.
*/
public function contains(&$value)
{
if (is_array($value)) {
return $this->containsArray($value);
} elseif (is_object($value)) {
return $this->containsObject($value);
}

throw new PHPUnit_Framework_Exception('Only arrays and objects are supported');
}

/**
* Stores an array within the context.
*
* @param array $value The value to store.
* @return integer The internal ID of the array.
*/
protected function addArray(array &$value)
{
if (($key = $this->containsArray($value)) !== false) {
return $key;
}

$this->arrays[] = &$value;
return count($this->arrays) - 1;
}

/**
* Stores an object within the context.
*
* @param object $value
* @return string The ID of the object.
*/
protected function addObject($value)
{
if (!$this->objects->contains($value)) {
$this->objects->attach($value);
}

return spl_object_hash($value);
}

/**
* Checks if the given array exists within the context.
*
* @param array $value The array to check.
* @return mixed The integer ID of the array if it exists, or boolean false
* otherwise.
*/
protected function containsArray(array &$value)
{
$keys = array_keys($this->arrays, $value, true);
$gen = '_PHPUnit_Test_Key_'.hash('sha512', microtime(true));
foreach ($keys as $key) {
$this->arrays[$key][$gen] = $gen;
if (isset($value[$gen]) && $value[$gen] === $gen) {
unset($this->arrays[$key][$gen]);
return $key;
}
unset($this->arrays[$key][$gen]);
}

return false;
}

/**
* Checks if the given object exists within the context.
*
* @param object $value The object to check.
* @return mixed The string ID of the object if it exists, or boolean false
* otherwise.
*/
protected function containsObject($value)
{
if ($this->objects->contains($value)) {
return spl_object_hash($value);
}

return false;
}
}

0 comments on commit 9267b0b

Please sign in to comment.