From 84a80d18c4777dd4d07358eb5fdc9ddccd575b57 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 25 Mar 2015 19:42:14 +0100 Subject: [PATCH] [VarDumper] Add filters to casters --- .../Component/VarDumper/Caster/Caster.php | 81 ++++++++++ .../VarDumper/Caster/ExceptionCaster.php | 10 +- .../VarDumper/Cloner/AbstractCloner.php | 13 +- .../VarDumper/Tests/Caster/CasterTest.php | 147 ++++++++++++++++++ 4 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Caster/Caster.php create mode 100644 src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php new file mode 100644 index 0000000000..716d9ce9f8 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas + */ +class Caster +{ + const EXCLUDE_VERBOSE = 1; + const EXCLUDE_VIRTUAL = 2; + const EXCLUDE_DYNAMIC = 4; + const EXCLUDE_PUBLIC = 8; + const EXCLUDE_PROTECTED = 16; + const EXCLUDE_PRIVATE = 32; + const EXCLUDE_NULL = 64; + const EXCLUDE_EMPTY = 128; + const EXCLUDE_NOT_IMPORTANT = 256; + const EXCLUDE_STRICT = 512; + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter. + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out. + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set. + * + * @return array The filtered array + */ + public static function filter(array $a, $filter, array $listedProperties = array()) + { + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + } + if (empty($v)) { + $type |= self::EXCLUDE_EMPTY & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + } + } + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index 7938da63e5..1f94ce91af 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -40,15 +40,17 @@ class ExceptionCaster E_STRICT => 'E_STRICT', ); - public static function castException(\Exception $e, array $a, Stub $stub, $isNested) + public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0) { $trace = $a["\0Exception\0trace"]; unset($a["\0Exception\0trace"]); // Ensures the trace is always last - static::filterTrace($trace, static::$traceArgs); + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + static::filterTrace($trace, static::$traceArgs); - if (null !== $trace) { - $a["\0Exception\0trace"] = $trace; + if (null !== $trace) { + $a["\0Exception\0trace"] = $trace; + } } if (empty($a["\0Exception\0previous"])) { unset($a["\0Exception\0previous"]); diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 8024b6443b..4b72721e62 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -96,6 +96,7 @@ abstract class AbstractCloner implements ClonerInterface private $casters = array(); private $prevErrorHandler; private $classInfo = array(); + private $filter = 0; /** * @param callable[]|null $casters A map of casters. @@ -149,10 +150,16 @@ abstract class AbstractCloner implements ClonerInterface } /** - * {@inheritdoc} + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable. + * @param int $filter A bit field of Caster::EXCLUDE_* constants. + * + * @return Data The cloned variable represented by a Data object. */ - public function cloneVar($var) + public function cloneVar($var, $filter = 0) { + $this->filter = $filter; $this->prevErrorHandler = set_error_handler(array($this, 'handleError')); try { if (!function_exists('iconv')) { @@ -270,7 +277,7 @@ abstract class AbstractCloner implements ClonerInterface private function callCaster($callback, $obj, $a, $stub, $isNested) { try { - $cast = call_user_func($callback, $obj, $a, $stub, $isNested); + $cast = call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter); if (is_array($cast)) { $a = $cast; diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php new file mode 100644 index 0000000000..d8ad81a877 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use Symfony\Component\VarDumper\Caster\Caster; + +/** + * @author Nicolas Grekas + */ +class CasterTest extends \PHPUnit_Framework_TestCase +{ + private $referenceArray = array( + 'null' => null, + 'empty' => false, + 'public' => 'pub', + "\0~\0virtual" => 'virt', + "\0+\0dynamic" => 'dyn', + "\0*\0protected" => 'prot', + "\0Foo\0private" => 'priv', + ); + + /** @dataProvider provideFilter */ + public function testFilter($filter, $expectedDiff, $listedProperties = null) + { + if (null === $listedProperties) { + $filteredArray = Caster::filter($this->referenceArray, $filter); + } else { + $filteredArray = Caster::filter($this->referenceArray, $filter, $listedProperties); + } + + $this->assertSame($expectedDiff, array_diff_assoc($this->referenceArray, $filteredArray)); + } + + public function provideFilter() + { + return array( + array( + 0, + array(), + ), + array( + Caster::EXCLUDE_PUBLIC, + array( + 'null' => null, + 'empty' => false, + 'public' => 'pub', + ), + ), + array( + Caster::EXCLUDE_NULL, + array( + 'null' => null, + ), + ), + array( + Caster::EXCLUDE_EMPTY, + array( + 'null' => null, + 'empty' => false, + ), + ), + array( + Caster::EXCLUDE_VIRTUAL, + array( + "\0~\0virtual" => 'virt', + ), + ), + array( + Caster::EXCLUDE_DYNAMIC, + array( + "\0+\0dynamic" => 'dyn', + ), + ), + array( + Caster::EXCLUDE_PROTECTED, + array( + "\0*\0protected" => 'prot', + ), + ), + array( + Caster::EXCLUDE_PRIVATE, + array( + "\0Foo\0private" => 'priv', + ), + ), + array( + Caster::EXCLUDE_VERBOSE, + array( + 'public' => 'pub', + "\0*\0protected" => 'prot', + ), + array('public', "\0*\0protected"), + ), + array( + Caster::EXCLUDE_NOT_IMPORTANT, + array( + 'null' => null, + 'empty' => false, + "\0~\0virtual" => 'virt', + "\0+\0dynamic" => 'dyn', + "\0Foo\0private" => 'priv', + ), + array('public', "\0*\0protected"), + ), + array( + Caster::EXCLUDE_VIRTUAL | Caster::EXCLUDE_DYNAMIC, + array( + "\0~\0virtual" => 'virt', + "\0+\0dynamic" => 'dyn', + ), + ), + array( + Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_VERBOSE, + $this->referenceArray, + array('public', "\0*\0protected"), + ), + array( + Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY, + array( + 'null' => null, + 'empty' => false, + "\0~\0virtual" => 'virt', + "\0+\0dynamic" => 'dyn', + "\0*\0protected" => 'prot', + "\0Foo\0private" => 'priv', + ), + array('public', 'empty'), + ), + array( + Caster::EXCLUDE_VERBOSE | Caster::EXCLUDE_EMPTY | Caster::EXCLUDE_STRICT, + array( + 'empty' => false, + ), + array('public', 'empty'), + ), + ); + } +}