diff --git a/composer.json b/composer.json index 4791a668..ca5e10ef 100644 --- a/composer.json +++ b/composer.json @@ -46,9 +46,9 @@ } }, "replace": { - "bdk/backtrace": "2.2", + "bdk/backtrace": "2.2.1", "bdk/curl-http-message": "1.0", - "bdk/errorhandler": "3.3.1", + "bdk/errorhandler": "3.3.2", "bdk/http-message": "1.1", "bdk/promise": "1.0", "bdk/pubsub": "3.2.1", diff --git a/src/Backtrace/Normalizer.php b/src/Backtrace/Normalizer.php index a1486ae2..7d940f16 100644 --- a/src/Backtrace/Normalizer.php +++ b/src/Backtrace/Normalizer.php @@ -20,6 +20,7 @@ class Normalizer private static $frameDefault = array( 'args' => array(), + 'evalLine' => null, 'file' => null, 'function' => null, // function, Class::function, or Class->function 'line' => null, @@ -42,7 +43,6 @@ public static function normalize($backtrace) 'params' => null, 'type' => null, ); - $frameKeysKeep = \array_merge(self::$frameDefault, \array_flip(array('evalLine'))); $count = \count($backtrace); $backtrace[] = array(); // add a frame so backtrace[$i + 1] is always a thing for ($i = 0; $i < $count; $i++) { @@ -59,7 +59,7 @@ public static function normalize($backtrace) // xdebug_get_function_stack $frame['args'] = self::normalizeXdebugParams($frame['params']); } - $frame = \array_intersect_key($frame, $frameKeysKeep); + $frame = \array_intersect_key($frame, self::$frameDefault); \ksort($frame); self::$backtraceTemp[] = $frame; } @@ -89,8 +89,8 @@ private static function normalizeFrameFile(array $frame, array &$frameNext) // reported line = line within eval // line inside paren is the line `eval` is on $frame['evalLine'] = $frame['line']; - $frame['line'] = (int) $matches[2]; $frame['file'] = $matches[1]; + $frame['line'] = (int) $matches[2]; if (isset($frameNext['include_filename'])) { // xdebug_get_function_stack puts the evaled code in include_filename $frameNext['params'] = array($frameNext['include_filename']); diff --git a/src/Debug/Abstraction/AbstractObject.php b/src/Debug/Abstraction/AbstractObject.php index b6704893..9fbcdd6b 100644 --- a/src/Debug/Abstraction/AbstractObject.php +++ b/src/Debug/Abstraction/AbstractObject.php @@ -26,54 +26,52 @@ use ReflectionClass; use ReflectionEnumUnitCase; use RuntimeException; -use UnitEnum; /** * Abstracter: Methods used to abstract objects */ class AbstractObject extends AbstractComponent { - // GENERAL - const PHPDOC_COLLECT = 1; // 2^0 - const PHPDOC_OUTPUT = 2; // 2^1 - const OBJ_ATTRIBUTE_COLLECT = 4; - const OBJ_ATTRIBUTE_OUTPUT = 8; - const TO_STRING_OUTPUT = 16; // 2^4 const BRIEF = 4194304; // 2^22 - // CONSTANTS - const CONST_COLLECT = 32; - const CONST_OUTPUT = 64; - const CONST_ATTRIBUTE_COLLECT = 128; - const CONST_ATTRIBUTE_OUTPUT = 256; // 2^8 - - // CASE + // CASE (2^9 - 2^12) const CASE_COLLECT = 512; const CASE_OUTPUT = 1024; const CASE_ATTRIBUTE_COLLECT = 2048; - const CASE_ATTRIBUTE_OUTPUT = 4096; // 2^12 + const CASE_ATTRIBUTE_OUTPUT = 4096; - // PROPERTIES - const PROP_ATTRIBUTE_COLLECT = 8192; - const PROP_ATTRIBUTE_OUTPUT = 16384; // 2^14 + // CONSTANTS (2^5 - 2^8) + const CONST_COLLECT = 32; + const CONST_OUTPUT = 64; + const CONST_ATTRIBUTE_COLLECT = 128; + const CONST_ATTRIBUTE_OUTPUT = 256; - // METHODS + // METHODS (2^15 - 2^21, 2^23 - 2^24) const METHOD_COLLECT = 32768; const METHOD_OUTPUT = 65536; + const METHOD_DESC_OUTPUT = 524288; const METHOD_ATTRIBUTE_COLLECT = 131072; const METHOD_ATTRIBUTE_OUTPUT = 262144; - const METHOD_DESC_OUTPUT = 524288; + const METHOD_STATIC_VAR_COLLECT = 8388608; // 2^23 + const METHOD_STATIC_VAR_OUTPUT = 16777216; // 2^24 + + const OBJ_ATTRIBUTE_COLLECT = 4; + const OBJ_ATTRIBUTE_OUTPUT = 8; + const PARAM_ATTRIBUTE_COLLECT = 1048576; - const PARAM_ATTRIBUTE_OUTPUT = 2097152; // 2^21 + const PARAM_ATTRIBUTE_OUTPUT = 2097152; + + const PHPDOC_COLLECT = 1; // 2^0 + const PHPDOC_OUTPUT = 2; - public static $cfgFlags = array( // @phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder - // GENERAL + // PROPERTIES (2^13 - 2^14) + const PROP_ATTRIBUTE_COLLECT = 8192; // 2^13 + const PROP_ATTRIBUTE_OUTPUT = 16384; // 2^14 + + const TO_STRING_OUTPUT = 16; // 2^4 + + public static $cfgFlags = array( 'brief' => self::BRIEF, - 'objAttributeCollect' => self::OBJ_ATTRIBUTE_COLLECT, - 'objAttributeOutput' => self::OBJ_ATTRIBUTE_OUTPUT, - 'phpDocCollect' => self::PHPDOC_COLLECT, - 'phpDocOutput' => self::PHPDOC_OUTPUT, - 'toStringOutput' => self::TO_STRING_OUTPUT, // CASE 'caseAttributeCollect' => self::CASE_ATTRIBUTE_COLLECT, @@ -93,12 +91,23 @@ class AbstractObject extends AbstractComponent 'methodCollect' => self::METHOD_COLLECT, 'methodDescOutput' => self::METHOD_DESC_OUTPUT, 'methodOutput' => self::METHOD_OUTPUT, + 'methodStaticVarCollect' => self::METHOD_STATIC_VAR_COLLECT, + 'methodStaticVarOutput' => self::METHOD_STATIC_VAR_OUTPUT, + + 'objAttributeCollect' => self::OBJ_ATTRIBUTE_COLLECT, + 'objAttributeOutput' => self::OBJ_ATTRIBUTE_OUTPUT, + 'paramAttributeCollect' => self::PARAM_ATTRIBUTE_COLLECT, 'paramAttributeOutput' => self::PARAM_ATTRIBUTE_OUTPUT, + 'phpDocCollect' => self::PHPDOC_COLLECT, + 'phpDocOutput' => self::PHPDOC_OUTPUT, + // PROPERTIES 'propAttributeCollect' => self::PROP_ATTRIBUTE_COLLECT, 'propAttributeOutput' => self::PROP_ATTRIBUTE_OUTPUT, + + 'toStringOutput' => self::TO_STRING_OUTPUT, ); protected $abstracter; @@ -137,32 +146,24 @@ class AbstractObject extends AbstractComponent * * @var array Array of key/values */ - protected static $values = array( // phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder - 'type' => Abstracter::TYPE_OBJECT, + protected static $values = array( 'cfgFlags' => 0, 'className' => '', 'debugMethod' => '', - 'isAnonymous' => false, + 'interfacesCollapse' => array(), // cfg.interfacesCollapse 'isExcluded' => false, // don't exclude if we're debugging directly 'isMaxDepth' => false, 'isRecursion' => false, + // methods may be populated with __toString info, or methods with staticVars 'properties' => array(), 'scopeClass' => '', + 'sectionOrder' => array(), // cfg.objectSectionOrder 'sort' => '', // cfg.objectSort 'stringified' => null, 'traverseValues' => array(), // populated if method is table 'viaDebugInfo' => false, ); - protected static $keysTemp = array( - 'collectPropertyValues', - 'fullyQualifyPhpDocType', - 'hist', - 'isTraverseOnly', - 'propertyOverrideValues', - 'reflector', - ); - /** * Constructor * @@ -202,12 +203,12 @@ public function getAbstraction($obj, $method = null, array $hist = array()) $reflector = $reflector->getEnum(); } $values = $this->getAbstractionValues($reflector, $obj, $method, $hist); - $valueStore = $this->definition->getValueStore($obj, $values); - $abs = new ObjectAbstraction($valueStore, $values); + $definitionValueStore = $this->definition->getAbstraction($obj, $values); + $abs = new ObjectAbstraction($definitionValueStore, $values); $abs->setSubject($obj); $abs['hist'][] = $obj; $this->doAbstraction($abs); - $this->absClean($abs); + $abs->clean(); return $abs; } @@ -233,21 +234,6 @@ public static function buildObjValues(array $values = array()) ); } - /** - * Sort things and remove temporary values - * - * @param ObjectAbstraction $abs Abstraction instance - * - * @return void - */ - private function absClean(ObjectAbstraction $abs) - { - $values = \array_diff_key($abs->getInstanceValues(), \array_flip(self::$keysTemp)); - $abs - ->setSubject(null) - ->setValues($values); - } - /** * Populate rows or columns (traverseValues) if we're outputing as a table * @@ -268,9 +254,10 @@ private function addTraverseValues(ObjectAbstraction $abs) } /** - * Add attributes, constants, properties, methods, constants, etc + * Collect instance info + * Property values & static method variables * - * @param ObjectAbstraction $abs Abstraction instance + * @param ObjectAbstraction $abs Object abstraction instance * * @return void */ @@ -280,11 +267,6 @@ private function doAbstraction(ObjectAbstraction $abs) return; } if ($abs['isRecursion']) { - if ($abs->getSubject() instanceof UnitEnum) { - $abs['properties']['name'] = array( - 'value' => $abs->getSubject()->name, - ); - } return; } $abs['isTraverseOnly'] = $this->isTraverseOnly($abs); @@ -304,7 +286,8 @@ private function doAbstraction(ObjectAbstraction $abs) if ($abs['isTraverseOnly']) { $this->addTraverseValues($abs); } - $this->properties->add($abs); + $this->methods->addInstance($abs); // method static variables + $this->properties->addInstance($abs); /* Debug::EVENT_OBJ_ABSTRACT_END subscriber has free reign to modify abtraction values */ @@ -312,7 +295,7 @@ private function doAbstraction(ObjectAbstraction $abs) } /** - * Returns information about an object + * Get initial "top-level" values. * * @param ReflectionClass $reflector Reflection instance * @param object|string $obj Object (or classname) to inspect @@ -330,11 +313,12 @@ protected function getAbstractionValues(ReflectionClass $reflector, $obj, $metho 'cfgFlags' => $this->getCfgFlags(), 'className' => $this->helper->getClassName($reflector), 'debugMethod' => $method, - 'isAnonymous' => PHP_VERSION_ID >= 70000 && $reflector->isAnonymous(), + 'interfacesCollapse' => \array_values(\array_intersect($reflector->getInterfaceNames(), $this->cfg['interfacesCollapse'])), 'isExcluded' => $hist && $this->isExcluded($obj), // don't exclude if we're debugging directly 'isMaxDepth' => $this->cfg['maxDepth'] && \count($hist) === $this->cfg['maxDepth'], 'isRecursion' => \in_array($obj, $hist, true), 'scopeClass' => $this->getScopeClass($hist), + 'sectionOrder' => $this->cfg['objectSectionOrder'], 'sort' => $this->cfg['objectSort'], 'viaDebugInfo' => $this->cfg['useDebugInfo'] && $reflector->hasMethod('__debugInfo'), ), @@ -355,7 +339,7 @@ protected function getAbstractionValues(ReflectionClass $reflector, $obj, $metho * * @return int bitmask */ - private function getCfgFlags() + protected function getCfgFlags() { $flagVals = \array_intersect_key(self::$cfgFlags, \array_filter($this->cfg)); $bitmask = \array_reduce($flagVals, static function ($carry, $val) { diff --git a/src/Debug/Abstraction/Abstracter.php b/src/Debug/Abstraction/Abstracter.php index 2e9c4917..ae6ae500 100644 --- a/src/Debug/Abstraction/Abstracter.php +++ b/src/Debug/Abstraction/Abstracter.php @@ -74,12 +74,30 @@ class Abstracter extends AbstractComponent protected $cfg = array( 'brief' => false, // collect & output less details 'fullyQualifyPhpDocType' => false, + 'interfacesCollapse' => array( + 'ArrayAccess', + 'BackedEnum', + 'Countable', + 'Iterator', + 'IteratorAggregate', + 'UnitEnum', + ), 'maxDepth' => 0, // value < 1 : no max-depth + 'objectSectionOrder' => array( + 'attributes', + 'extends', + 'implements', + 'constants', + 'cases', + 'properties', + 'methods', + 'phpDoc', + ), 'objectsExclude' => array( // __NAMESPACE__ added in constructor 'DOMNode', ), - 'objectSort' => 'visibility', // none, visibility, or name + 'objectSort' => 'inheritance visibility name', 'objectsWhitelist' => null, // will be used if array 'stringMaxLen' => array( 'base64' => 156, // 2 lines of chunk_split'ed @@ -470,7 +488,6 @@ protected function postSetCfg($cfg = array(), $prev = array()) $debugClass = \get_class($this->debug); if (!\array_intersect(array('*', $debugClass), $this->cfg['objectsExclude'])) { $this->cfg['objectsExclude'][] = $debugClass; - $cfg['objectsExclude'] = $this->cfg['objectsExclude']; } if (isset($cfg['stringMaxLen'])) { if (\is_array($cfg['stringMaxLen']) === false) { @@ -478,14 +495,18 @@ protected function postSetCfg($cfg = array(), $prev = array()) 'other' => $cfg['stringMaxLen'], ); } - $cfg['stringMaxLen'] = \array_merge($prev['stringMaxLen'], $cfg['stringMaxLen']); - $this->cfg['stringMaxLen'] = $cfg['stringMaxLen']; + $this->cfg['stringMaxLen'] = \array_merge($prev['stringMaxLen'], $cfg['stringMaxLen']); } if (isset($cfg['stringMinLen'])) { - $cfg['stringMinLen'] = \array_merge($prev['stringMinLen'], $cfg['stringMinLen']); - $this->cfg['stringMinLen'] = $cfg['stringMinLen']; + $this->cfg['stringMinLen'] = \array_merge($prev['stringMinLen'], $cfg['stringMinLen']); + } + if (isset($cfg['objectSectionOrder'])) { + $oso = \array_intersect($cfg['objectSectionOrder'], $prev['objectSectionOrder']); + $oso = \array_merge($oso, $prev['objectSectionOrder']); + $oso = \array_unique($oso); + $this->cfg['objectSectionOrder'] = $oso; } - $this->setCfgDependencies($cfg); + $this->setCfgDependencies(\array_intersect_key($this->cfg, $cfg)); } /** diff --git a/src/Debug/Abstraction/Object/Abstraction.php b/src/Debug/Abstraction/Object/Abstraction.php index 3450a3ee..77ab1c49 100644 --- a/src/Debug/Abstraction/Object/Abstraction.php +++ b/src/Debug/Abstraction/Object/Abstraction.php @@ -22,19 +22,29 @@ */ class Abstraction extends BaseAbstraction { - private $definition; + protected static $keysTemp = array( + 'collectPropertyValues', + 'fullyQualifyPhpDocType', + 'hist', + 'isTraverseOnly', + 'propertyOverrideValues', + 'reflector', + ); + + /** @var ValueStore */ + private $inherited; private $sortableValues = array('attributes', 'cases', 'constants', 'methods', 'properties'); /** * Constructor * - * @param ValueStore $definition class definition values - * @param array $values abtraction values + * @param ValueStore $inherited Inherited values + * @param array $values Abtraction values */ - public function __construct(ValueStore $definition, $values = array()) + public function __construct(ValueStore $inherited, $values = array()) { - $this->definition = $definition; + $this->inherited = $inherited; parent::__construct(Abstracter::TYPE_OBJECT, $values); } @@ -43,7 +53,7 @@ public function __construct(ValueStore $definition, $values = array()) */ public function __serialize() { - return $this->getInstanceValues() + array('classDefinition' => $this->definition); + return $this->getInstanceValues() + array('inherited' => $this->inherited); } /** @@ -67,13 +77,24 @@ public function __toString() */ public function __unserialize($data) { - $this->definition = isset($data['classDefinition']) - ? $data['classDefinition'] + $this->inherited = isset($data['inherited']) + ? $data['inherited'] : new ValueStore(); - unset($data['classDefinition']); + unset($data['inherited']); $this->values = $data; } + /** + * Remove temporary values + * + * @return void + */ + public function clean() + { + $this->values = \array_diff_key($this->values, \array_flip(self::$keysTemp)); + $this->setSubject(null); + } + /** * Implements JsonSerializable * @@ -83,8 +104,9 @@ public function __unserialize($data) public function jsonSerialize() { return $this->getInstanceValues() + array( - 'classDefinition' => $this->definition['className'], 'debug' => Abstracter::ABSTRACTION, + 'inheritsFrom' => $this->inherited['className'], + 'type' => Abstracter::TYPE_OBJECT, ); } @@ -93,9 +115,11 @@ public function jsonSerialize() * * @return array */ - public function getDefinitionValues() + public function getInheritedValues() { - return $this->definition->getValues(); + $values = $this->inherited->getValues(); + unset($values['cfgFlags']); + return $values; } /** @@ -104,7 +128,7 @@ public function getDefinitionValues() public function getValues() { return \array_replace_recursive( - $this->getDefinitionValues(), + $this->getInheritedValues(), $this->values ); } @@ -118,7 +142,7 @@ public function getInstanceValues() { return ArrayUtil::diffAssocRecursive( $this->values, - $this->getDefinitionValues() + $this->getInheritedValues() ); } @@ -130,36 +154,31 @@ public function getInstanceValues() * * @return array */ - public function sort(array $array, $order = 'visibility') + public function sort(array $array, $order = 'visibility, name') { - $sortVisOrder = array('public', 'magic', 'magic-read', 'magic-write', 'protected', 'private', 'debug'); - $sortData = array( - 'name' => array(), - 'vis' => array(), + $order = \preg_split('/[,\s]+/', (string) $order); + $aliases = array( + 'name' => 'name', + 'vis' => 'vis', + 'visibility' => 'vis', ); - foreach ($array as $name => $info) { - if ($name === '__construct') { - // always place __construct at the top - $sortData['name'][$name] = -1; - $sortData['vis'][$name] = 0; + foreach ($order as $i => $what) { + if (isset($aliases[$what]) === false) { + unset($order[$i]); continue; } - $vis = isset($info['visibility']) - ? $info['visibility'] - : '?'; - if (\is_array($vis)) { - // Sort the visiblity so we use the most significant vis - ArrayUtil::sortWithOrder($vis, $sortVisOrder); - $vis = $vis[0]; - } - $sortData['name'][$name] = \strtolower($name); - $sortData['vis'][$name] = \array_search($vis, $sortVisOrder, true); + $order[$i] = $aliases[$what]; + } + if (empty($order)) { + return $array; } - if ($order === 'name') { - \array_multisort($sortData['name'], $array); - } elseif ($order === 'visibility') { - \array_multisort($sortData['vis'], $sortData['name'], $array); + $multiSortArgs = array(); + $sortData = $this->sortData($array); + foreach ($order as $what) { + $multiSortArgs[] = $sortData[$what]; } + $multiSortArgs[] = &$array; + \call_user_func_array('array_multisort', $multiSortArgs); return $array; } @@ -172,7 +191,7 @@ public function offsetExists($key) if (\array_key_exists($key, $this->values)) { return $this->values[$key] !== null; } - return isset($this->definition[$key]); + return isset($this->inherited[$key]); } /** @@ -198,10 +217,19 @@ private function getCombinedValue($key) $value = isset($this->values[$key]) ? $this->values[$key] : null; - $classVal = \in_array($key, $this->sortableValues, true) - && ($this->values['isRecursion'] || $this->values['isExcluded']) - ? array() // don't inherit - : $this->definition[$key]; + $inherit = true; + if (\in_array($key, $this->sortableValues, true)) { + $combined = \array_merge(array( + 'isExcluded' => $this->inherited['isExcluded'], + 'isRecursion' => $this->inherited['isRecursion'], + ), $this->values); + if ($combined['isExcluded'] || $combined['isRecursion']) { + $inherit = false; + } + } + $classVal = $inherit + ? $this->inherited[$key] + : array(); if ($value !== null) { return \is_array($classVal) ? \array_replace_recursive($classVal, $value) @@ -209,4 +237,39 @@ private function getCombinedValue($key) } return $classVal; } + + /** + * Collect sort data to be used by `array_multisort` + * + * @param array $array The array of methods or properties to be sorted + * + * @return array + */ + protected function sortData(array $array) + { + $sortVisOrder = array('public', 'magic', 'magic-read', 'magic-write', 'protected', 'private', 'debug'); + $sortData = array( + 'name' => array(), + 'vis' => array(), + ); + foreach ($array as $name => $info) { + if ($name === '__construct') { + // always place __construct at the top + $sortData['name'][$name] = -1; + $sortData['vis'][$name] = 0; + continue; + } + $vis = isset($info['visibility']) + ? $info['visibility'] + : '?'; + if (\is_array($vis)) { + // Sort the visiblity so we use the most significant vis + ArrayUtil::sortWithOrder($vis, $sortVisOrder); + $vis = $vis[0]; + } + $sortData['name'][$name] = \strtolower($name); + $sortData['vis'][$name] = \array_search($vis, $sortVisOrder, true); + } + return $sortData; + } } diff --git a/src/Debug/Abstraction/Object/Definition.php b/src/Debug/Abstraction/Object/Definition.php index 5e4802d3..8311dc22 100644 --- a/src/Debug/Abstraction/Object/Definition.php +++ b/src/Debug/Abstraction/Object/Definition.php @@ -15,7 +15,9 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; use bdk\PubSub\ValueStore; +use ReflectionClass; /** * Abstracter: Gather class definition info common across all instances @@ -26,10 +28,11 @@ class Definition protected $debug; protected $helper; protected $methods; + protected $object; protected $properties; /** @var ValueStore|null base/default class values */ - protected static $default; + protected $default; /** * @var array Array of key/values @@ -41,15 +44,17 @@ class Definition 'className' => "\x00default\x00", 'constants' => array(), 'definition' => array( - 'extensionName' => '', - 'fileName' => '', - 'startLine' => 1, + 'extensionName' => false, + 'fileName' => false, + 'startLine' => false, ), 'extends' => array(), 'implements' => array(), + 'isAnonymous' => false, 'isFinal' => false, 'isReadOnly' => false, 'methods' => array(), + 'methodsWithStaticVars' => array(), 'phpDoc' => array( 'desc' => null, 'summary' => null, @@ -65,77 +70,41 @@ class Definition public function __construct(AbstractObject $abstractObject) { $this->debug = $abstractObject->debug; + $this->object = $abstractObject; $this->helper = $abstractObject->helper; $this->constants = $abstractObject->constants; $this->methods = $abstractObject->methods; $this->properties = $abstractObject->properties; } - /** - * Get class abstraction - * - * @param object $obj Object being abstracted - * @param array $info values already collected - * - * @return Abstraction - */ - public function getAbstraction($obj, array $info = array()) - { - $abs = $this->getAbstractionInit($info); - $abs->setSubject($obj); - $abs['fullyQualifyPhpDocType'] = $info['fullyQualifyPhpDocType']; - $abs['reflector'] = $info['reflector']; - - $this->addDefinition($abs); - $this->addAttributes($abs); - $this->constants->add($abs); - $this->constants->addCases($abs); - $this->methods->add($abs); - $this->properties->addClass($abs); - if ($abs['className'] === 'Closure') { - // __incoke is "unique" per instance - $abs['methods']['__invoke'] = array(); - } - - unset($abs['fullyQualifyPhpDocType']); - unset($abs['reflector']); - return $abs; - } - /** * Get class ValueStore obj * - * @param object $obj Object being abstracted - * @param array $info Instance abstraction info + * @param object $obj Object being abstracted + * @param array $values Instance values * * @return ValueStore */ - public function getValueStore($obj, array $info) + public function getAbstraction($obj, array $values) { - $className = $info['className']; - $reflector = $info['reflector']; - if ($info['isAnonymous']) { - if ($reflector->getParentClass() === false) { - return $this->getValueStoreDefault(); - } - $reflector = $reflector->getParentClass(); - $className = $reflector->getName(); - $info['reflector'] = $reflector; - } - $dataPath = array('classDefinitions', $className); + $className = $values['className']; + $reflector = $values['reflector']; + $valueStoreKey = PHP_VERSION_ID >= 70000 && $reflector->isAnonymous() + ? $className . '|' . \md5($reflector->getName()) + : $className; + $dataPath = array('classDefinitions', $valueStoreKey); $valueStore = $this->debug->data->get($dataPath); if ($valueStore) { return $valueStore; } - if (\array_filter(array($info['isMaxDepth'], $info['isExcluded']))) { + if (\array_filter(array($values['isMaxDepth'], $values['isExcluded']))) { return $this->getValueStoreDefault(); } - $valueStore = new ValueStore(); - $this->debug->data->set($dataPath, $valueStore); - $classAbs = $this->getAbstraction($obj, $info); - $valueStore->setValues($classAbs->getValues()); - unset($valueStore['type']); - return $valueStore; + $abs = new ObjectAbstraction($this->getValueStoreDefault(), $this->getInitValues($values)); + $abs->setSubject($obj); + $this->doAbstraction($abs); + $this->debug->data->set($dataPath, $abs); + return $abs; } /** @@ -145,12 +114,22 @@ public function getValueStore($obj, array $info) */ public function getValueStoreDefault() { - if (self::$default) { - return self::$default; + if ($this->default) { + return $this->default; } $key = self::$values['className']; - $classValueStore = new ValueStore(self::$values); - self::$default = $classValueStore; + $classValueStore = new ValueStore(\array_merge( + self::$values, + array( + 'isExcluded' => false, + 'sectionOrder' => $this->object->getCfg('objectSectionOrder'), + 'sort' => $this->object->getCfg('objectSort'), + 'stringified' => null, + 'traverseValues' => array(), + 'viaDebugInfo' => false, + ) + )); + $this->default = $classValueStore; $this->debug->data->set(array( 'classDefinitions', $key, @@ -161,22 +140,22 @@ public function getValueStoreDefault() /** * Collect class attributes * - * @param ValueStore $abs Object abstraction instance + * @param ValueStore $abs ValueStore instance * * @return void */ public function addAttributes(ValueStore $abs) { - $reflector = $abs['reflector']; - $abs['attributes'] = $abs['cfgFlags'] & AbstractObject::OBJ_ATTRIBUTE_COLLECT - ? $this->helper->getAttributes($reflector) - : array(); + if ($abs['cfgFlags'] & AbstractObject::OBJ_ATTRIBUTE_COLLECT) { + $reflector = $abs['reflector']; + $abs['attributes'] = $this->helper->getAttributes($reflector); + } } /** * Collect "definition" values * - * @param ValueStore $abs Object abstraction instance + * @param ValueStore $abs ValueStore instance * * @return void */ @@ -192,37 +171,135 @@ public function addDefinition(ValueStore $abs) ); } + /** + * Collect interfaces that object implements + * + * @param ValueStore $abs ValueStore instance + * + * @return void + */ + public function addImplements(ValueStore $abs) + { + $abs['implements'] = $this->getInterfaces($abs['reflector']); + } + + /** + * Collect phpDoc summary/description/params + * + * @param ValueStore $abs ValueStore instance + * + * @return void + */ + public function addPhpDoc(ValueStore $abs) + { + $reflector = $abs['reflector']; + $fullyQualifyType = $abs['fullyQualifyPhpDocType']; + $phpDoc = $this->helper->getPhpDoc($reflector, $fullyQualifyType); + while ( + ($reflector = $reflector->getParentClass()) + && $phpDoc === array('desc' => null, 'summary' => null) + ) { + $phpDoc = $this->helper->getPhpDoc($reflector, $fullyQualifyType); + } + unset($phpDoc['method']); + // magic properties removed via PropertiesPhpDoc::addViaPhpDocIter + $abs['phpDoc'] = $phpDoc; + } + + /** + * Collect runtime info + * attributes, constants, properties, methods, etc + * + * @param ObjectAbstraction $abs Object abstraction instance + * + * @return void + */ + protected function doAbstraction(ObjectAbstraction $abs) + { + $this->addAttributes($abs); + $this->addDefinition($abs); + $this->addImplements($abs); + $this->addPhpDoc($abs); + $this->constants->add($abs); + $this->constants->addCases($abs); + $this->methods->add($abs); + $this->properties->add($abs); + + if ($abs['className'] === 'Closure') { + // __invoke is "unique" per instance + $abs['methods']['__invoke'] = array(); + } + + $abs->clean(); + } + + /** + * Get a structured interface tree + * + * @param ReflectionClass $reflector ReflectionClass + * + * @return array + */ + protected function getInterfaces(ReflectionClass $reflector) + { + $interfaces = array(); + $remove = array(); + foreach ($reflector->getInterfaces() as $classname => $refClass) { + if (\in_array($classname, $remove, true)) { + continue; + } + $extends = $refClass->getInterfaceNames(); + if ($extends) { + $interfaces[$classname] = $this->getInterfaces($refClass); + $remove = \array_merge($remove, $extends); + continue; + } + $interfaces[] = $classname; + } + $remove = \array_unique($remove); + $interfaces = \array_diff_key($interfaces, \array_flip($remove)); + // remove values... array_diff complains about array to string conversion + foreach ($remove as $classname) { + $key = \array_search($classname, $interfaces, true); + if ($key !== false) { + unset($interfaces[$key]); + } + } + return $interfaces; + } + /** * Initialize class definition abstraction * - * @param array $info values already collected + * @param array $values values already collected * * @return Absttraction */ - protected function getAbstractionInit(array $info) + protected function getInitValues(array $values) { - $reflector = $info['reflector']; - $interfaceNames = $reflector->getInterfaceNames(); - \sort($interfaceNames); + $reflector = $values['reflector']; + $isAnonymous = PHP_VERSION_ID >= 70000 && $reflector->isAnonymous(); $values = \array_merge( self::$values, array( - 'cfgFlags' => $info['cfgFlags'], - 'className' => $reflector->getName(), - 'implements' => $interfaceNames, + 'cfgFlags' => $values['cfgFlags'], + 'className' => $isAnonymous + ? $values['className'] . '|' . \md5($reflector->getName()) + : $values['className'], + 'isAnonymous' => $isAnonymous, 'isFinal' => $reflector->isFinal(), 'isReadOnly' => PHP_VERSION_ID >= 80200 && $reflector->isReadOnly(), - 'phpDoc' => $this->helper->getPhpDoc($reflector, $info['fullyQualifyPhpDocType']), + ), + array( + // these are temporary values available during abstraction + 'fullyQualifyPhpDocType' => $values['fullyQualifyPhpDocType'], + 'hist' => array(), + 'reflector' => $values['reflector'], ) ); while ($reflector = $reflector->getParentClass()) { - if ($values['phpDoc'] === array('desc' => null, 'summary' => null)) { - $values['phpDoc'] = $this->helper->getPhpDoc($reflector, $info['fullyQualifyPhpDocType']); - } $values['extends'][] = $reflector->getName(); } - unset($values['phpDoc']['method']); - // magic properties removed via PropertiesPhpDoc::addViaPhpDocIter - return new Abstraction(Abstracter::TYPE_OBJECT, $values); + return $values; } } diff --git a/src/Debug/Abstraction/Object/Methods.php b/src/Debug/Abstraction/Object/Methods.php index 691266ed..5a7b59ac 100644 --- a/src/Debug/Abstraction/Object/Methods.php +++ b/src/Debug/Abstraction/Object/Methods.php @@ -46,6 +46,7 @@ class Methods extends Inheritable 'desc' => null, 'type' => null, ), + 'staticVars' => array(), 'visibility' => 'public', // public | private | protected | magic ); @@ -77,6 +78,28 @@ public function add(Abstraction $abs) } } + /** + * Add static variable info to abstraction + * + * @param Abstraction $abs Object Abstraction instance + * + * @return void + */ + public function addInstance(Abstraction $abs) + { + $staticVarCollect = $abs['cfgFlags'] & AbstractObject::METHOD_COLLECT + && $abs['cfgFlags'] & AbstractObject::METHOD_STATIC_VAR_COLLECT; + if ($staticVarCollect === false) { + return; + } + foreach ($abs['methodsWithStaticVars'] as $name) { + $reflector = $abs['reflector']->getMethod($name); + $abs['methods'][$name]['staticVars'] = \array_map(function ($value) use ($abs) { + return $this->abstracter->crate($value, $abs['debugMethod'], $abs['hist']); + }, $reflector->getStaticVariables()); + } + } + /** * Return method info array * @@ -138,8 +161,10 @@ private function addMethodsFull(Abstraction $abs) } $methods = $abs['methods']; foreach ($methods as &$methodInfo) { - $methodInfo['phpDoc']['desc'] = null; - $methodInfo['phpDoc']['summary'] = null; + $methodInfo['phpDoc'] = array( + 'desc' => null, + 'summary' => null, + ); foreach (\array_keys($methodInfo['params']) as $index) { $methodInfo['params'][$index]['desc'] = null; } @@ -180,19 +205,19 @@ private function addMethodsMin(Abstraction $abs) */ private function addImplements(Abstraction $abs) { - $interfaceMethods = array( - 'ArrayAccess' => array('offsetExists','offsetGet','offsetSet','offsetUnset'), - 'BackedEnum' => array('from', 'tryFrom'), - 'Countable' => array('count'), - 'Iterator' => array('current','key','next','rewind','valid'), - 'IteratorAggregate' => array('getIterator'), - 'UnitEnum' => array('cases'), - ); - $interfaces = \array_intersect($abs['reflector']->getInterfaceNames(), \array_keys($interfaceMethods)); - foreach ($interfaces as $interface) { - foreach ($interfaceMethods[$interface] as $methodName) { - // this method implements this interface - $abs['methods'][$methodName]['implements'] = $interface; + $stack = $abs['implements']; + while ($stack) { + $key = \key($stack); + $val = \array_shift($stack); + $classname = $val; + if (\is_array($val)) { + $classname = $key; + $stack = \array_merge($stack, $val); + } + $refClass = new ReflectionClass($classname); + foreach ($refClass->getMethods() as $refMethod) { + $methodName = $refMethod->getName(); + $abs['methods'][$methodName]['implements'] = $classname; } } } @@ -260,7 +285,8 @@ private function addViaPhpDocInherit(Abstraction $abs, &$declaredLast = null) private function addViaReflection(Abstraction $abs) { $methods = array(); - $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs, &$methods) { + $withStaticVars = array(); + $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs, &$methods, &$withStaticVars) { $className = $this->helper->getClassName($reflector); $refMethods = $reflector->getMethods(); while ($refMethods) { @@ -279,12 +305,17 @@ private function addViaReflection(Abstraction $abs) // getMethods() returns parent's private methods (#reasons).. we'll skip it continue; } + if (!empty($info['hasStaticVars'])) { + $withStaticVars[] = $name; + } + unset($info['hasStaticVars']); unset($info['phpDoc']['param']); unset($info['phpDoc']['return']); $methods[$name] = $info; } }); \ksort($methods); + $abs['methodsWithStaticVars'] = $withStaticVars; $abs['methods'] = $methods; } @@ -333,6 +364,7 @@ private function buildMethodRef(Abstraction $abs, ReflectionMethod $refMethod) 'attributes' => $abs['cfgFlags'] & AbstractObject::METHOD_ATTRIBUTE_COLLECT ? $this->helper->getAttributes($refMethod) : array(), + 'hasStaticVars' => \count($refMethod->getStaticVariables()) > 0, // temporary we don't store the values in the definition, only what methods have static vars 'isAbstract' => $refMethod->isAbstract(), 'isDeprecated' => $refMethod->isDeprecated() || isset($phpDoc['deprecated']), 'isFinal' => $refMethod->isFinal(), diff --git a/src/Debug/Abstraction/Object/Properties.php b/src/Debug/Abstraction/Object/Properties.php index dae40383..8539e39d 100644 --- a/src/Debug/Abstraction/Object/Properties.php +++ b/src/Debug/Abstraction/Object/Properties.php @@ -59,7 +59,7 @@ public function __construct(AbstractObject $abstractObject) } /** - * Add property instance info/values to abstraction + * Add declared property info * * @param Abstraction $abs Object Abstraction instance * @@ -67,34 +67,56 @@ public function __construct(AbstractObject $abstractObject) */ public function add(Abstraction $abs) { - if ($abs['isTraverseOnly']) { - return; + $this->addViaRef($abs); + $this->phpDoc->addViaPhpDoc($abs); // magic properties documented via phpDoc + + $properties = $abs['properties']; + + // note: for user-defined classes getDefaultProperties + // will return the current value for static properties + $defaultValues = $abs['reflector']->getDefaultProperties(); + foreach ($defaultValues as $name => $value) { + $properties[$name]['value'] = $value; } - $this->addValues($abs); - $obj = $abs->getSubject(); - if (\is_object($obj)) { - $this->addDebug($abs); // use __debugInfo() values if useDebugInfo' && method exists + + if ($abs['isAnonymous']) { + $properties['debug.file'] = static::buildPropValues(array( + 'type' => Abstracter::TYPE_STRING, + 'value' => $abs['definition']['fileName'], + 'valueFrom' => 'debug', + 'visibility' => 'debug', + )); + $properties['debug.line'] = static::buildPropValues(array( + 'type' => Abstracter::TYPE_INT, + 'value' => (int) $abs['definition']['startLine'], + 'valueFrom' => 'debug', + 'visibility' => 'debug', + )); } + + $abs['properties'] = $properties; + $this->crate($abs); } /** - * Add declared property info + * Add property instance info/values to abstraction * * @param Abstraction $abs Object Abstraction instance * * @return void */ - public function addClass(Abstraction $abs) + public function addInstance(Abstraction $abs) { - $this->addViaRef($abs); - $this->phpDoc->addViaPhpDoc($abs); // magic properties documented via phpDoc - $defaultValues = $abs['reflector']->getDefaultProperties(); - $properties = $abs['properties']; - foreach ($defaultValues as $name => $value) { - $properties[$name]['value'] = $value; + if ($abs['isTraverseOnly']) { + return; } - $abs['properties'] = $properties; + $this->addValues($abs); + $obj = $abs->getSubject(); + if (\is_object($obj)) { + $this->addDebug($abs); // use __debugInfo() values if useDebugInfo' && method exists + } + $this->crate($abs); } /** @@ -300,14 +322,13 @@ private function buildViaRef(Abstraction $abs, ReflectionProperty $refProperty) { $phpDoc = $this->helper->getPhpDocVar($refProperty, $abs['fullyQualifyPhpDocType']); $refProperty->setAccessible(true); // only accessible via reflection - /* - getDeclaringClass returns "LAST-declared/overriden" - */ return static::buildPropValues(array( 'attributes' => $abs['cfgFlags'] & AbstractObject::PROP_ATTRIBUTE_COLLECT ? $this->helper->getAttributes($refProperty) : array(), - 'desc' => $phpDoc['desc'], + 'desc' => $abs['cfgFlags'] & AbstractObject::PHPDOC_COLLECT + ? $phpDoc['desc'] + : null, 'isPromoted' => PHP_VERSION_ID >= 80000 ? $refProperty->isPromoted() : false, @@ -330,12 +351,8 @@ private function buildViaRef(Abstraction $abs, ReflectionProperty $refProperty) private function crate(Abstraction $abs) { $properties = $abs['properties']; - $phpDocCollect = $abs['cfgFlags'] & AbstractObject::PHPDOC_COLLECT; foreach ($properties as $name => $info) { $info['value'] = $this->abstracter->crate($info['value'], $abs['debugMethod'], $abs['hist']); - if (!$phpDocCollect) { - $info['desc'] = null; - } $properties[$name] = $info; } $abs['properties'] = $properties; diff --git a/src/Debug/Abstraction/Object/PropertiesPhpDoc.php b/src/Debug/Abstraction/Object/PropertiesPhpDoc.php index c8494cbf..cf2f48db 100644 --- a/src/Debug/Abstraction/Object/PropertiesPhpDoc.php +++ b/src/Debug/Abstraction/Object/PropertiesPhpDoc.php @@ -13,6 +13,7 @@ namespace bdk\Debug\Abstraction\Object; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Abstraction\Object\Helper; /** @@ -133,7 +134,9 @@ private function buildViaPhpDoc(Abstraction $abs, $phpDocProp, $declaredLast, $v $existing ?: Properties::buildPropValues(), // self::$basePropInfo array( 'declaredLast' => $declaredLast, - 'desc' => $phpDocProp['desc'], + 'desc' => $abs['cfgFlags'] & AbstractObject::PHPDOC_COLLECT + ? $phpDocProp['desc'] + : null, 'type' => $phpDocProp['type'], 'visibility' => $existing ? array($vis, $existing['visibility']) // we want "magic" visibility first diff --git a/src/Debug/Abstraction/Object/Subscriber.php b/src/Debug/Abstraction/Object/Subscriber.php index 1a2c0384..4de7812f 100644 --- a/src/Debug/Abstraction/Object/Subscriber.php +++ b/src/Debug/Abstraction/Object/Subscriber.php @@ -68,23 +68,28 @@ public function getSubscriptions() public function onStart(Abstraction $abs) { $obj = $abs->getSubject(); - if ($obj instanceof \DateTime || $obj instanceof \DateTimeImmutable) { - // check for both DateTime and DateTimeImmutable - // DateTimeInterface (and DateTimeImmutable) not available until Php 5.5 - $abs['isTraverseOnly'] = false; - $abs['stringified'] = $obj->format(\DateTime::ISO8601); - } elseif ($obj instanceof mysqli) { - $this->onStartMysqli($abs); - } elseif ($obj instanceof Data) { - $abs['propertyOverrideValues']['data'] = Abstracter::NOT_INSPECTED; - } elseif ($obj instanceof PhpDoc) { - $abs['propertyOverrideValues']['cache'] = Abstracter::NOT_INSPECTED; - } elseif ($obj instanceof AbstractObjectDefinition) { - $abs['propertyOverrideValues']['cache'] = Abstracter::NOT_INSPECTED; - } elseif ($abs['isAnonymous']) { - $this->onStartAnonymous($abs); - } elseif ($abs['className'] === 'Closure') { - $this->onStartClosure($abs); + switch (true) { + case $obj instanceof \DateTime || $obj instanceof \DateTimeImmutable: + // check for both DateTime and DateTimeImmutable + // DateTimeInterface (and DateTimeImmutable) not available until Php 5.5 + $abs['isTraverseOnly'] = false; + $abs['stringified'] = $obj->format(\DateTime::ISO8601); + break; + case $obj instanceof mysqli: + $this->onStartMysqli($abs); + break; + case $obj instanceof Data: + $abs['propertyOverrideValues']['data'] = Abstracter::NOT_INSPECTED; + break; + case $obj instanceof PhpDoc: + $abs['propertyOverrideValues']['cache'] = Abstracter::NOT_INSPECTED; + break; + case $obj instanceof AbstractObjectDefinition: + $abs['propertyOverrideValues']['cache'] = Abstracter::NOT_INSPECTED; + break; + case $abs['className'] === 'Closure': + $this->onStartClosure($abs); + break; } } @@ -164,44 +169,6 @@ private function onEndMysqli(Abstraction $abs) \restore_error_handler(); } - /** - * Add anonymous instance info - * - * * definition - * * constants - * * methods - * * add file & line debug properties - * - * @param Abstraction $abs Abstraction instance - * - * @return void - */ - private function onStartAnonymous(Abstraction $abs) - { - $this->abstractObject->definition->addDefinition($abs); - $this->abstractObject->constants->add($abs); - $this->abstractObject->methods->add($abs); - if ($abs['reflector']->getParentClass()) { - $abs['extends'] = \array_merge(array( - $abs['reflector']->getParentClass()->getName(), - ), $abs['extends']); - } - $properties = $abs['properties']; - $properties['debug.file'] = $this->abstractObject->properties->buildPropValues(array( - 'type' => Abstracter::TYPE_STRING, - 'value' => $abs['definition']['fileName'], - 'valueFrom' => 'debug', - 'visibility' => 'debug', - )); - $properties['debug.line'] = $this->abstractObject->properties->buildPropValues(array( - 'type' => Abstracter::TYPE_INT, - 'value' => (int) $abs['definition']['startLine'], - 'valueFrom' => 'debug', - 'visibility' => 'debug', - )); - $abs['properties'] = $properties; - } - /** * Set Closure definition and debug properties * diff --git a/src/Debug/Config.php b/src/Debug/Config.php index 76037aee..5dd05214 100644 --- a/src/Debug/Config.php +++ b/src/Debug/Config.php @@ -36,14 +36,18 @@ class Config 'constCollect', 'constOutput', 'fullyQualifyPhpDocType', + 'interfacesCollapse', 'maxDepth', 'methodAttributeCollect', 'methodAttributeOutput', 'methodCollect', 'methodDescOutput', 'methodOutput', + 'methodStaticVarCollect', + 'methodStaticVarOutput', 'objAttributeCollect', 'objAttributeOutput', + 'objectSectionOrder', 'objectsExclude', 'objectSort', 'objectsWhitelist', diff --git a/src/Debug/Debug.php b/src/Debug/Debug.php index 0ffff3e6..6130731a 100644 --- a/src/Debug/Debug.php +++ b/src/Debug/Debug.php @@ -111,7 +111,7 @@ class Debug extends AbstractDebug const EVENT_STREAM_WRAP = 'debug.streamWrap'; const META = "\x00meta\x00"; - const VERSION = '3.1'; + const VERSION = '3.2'; protected $cfg = array( 'channelIcon' => 'fa fa-list-ul', diff --git a/src/Debug/Dump/Html.php b/src/Debug/Dump/Html.php index 8dbf92f6..1a2a7cf0 100644 --- a/src/Debug/Dump/Html.php +++ b/src/Debug/Dump/Html.php @@ -78,11 +78,7 @@ public function processLogEntry(LogEntry $logEntry) 'data-icon' => $meta['icon'], ), $meta['attribs']); $html = parent::processLogEntry($logEntry); - $html = \strtr($html, array( - ' data-channel="null"' => '', - ' data-detect-files="null"' => '', - ' data-icon="null"' => '', - )); + $html = \preg_replace('/ data-[-\w]+="null"/', '', $html); return $html . "\n"; } @@ -226,7 +222,9 @@ protected function methodDefault(LogEntry $logEntry) if (isset($meta['file']) && $logEntry->getChannelName() !== $this->channelNameRoot . '.phpError') { // PHP errors will have file & line as one of the arguments // so no need to store file & line as data args + $meta = \array_merge(array('evalLine' => null), $meta); $attribs = \array_merge(array( + 'data-evalLine' => $meta['evalLine'], 'data-file' => $meta['file'], 'data-line' => $meta['line'], ), $attribs); diff --git a/src/Debug/Dump/Html/AbstractObjectSection.php b/src/Debug/Dump/Html/AbstractObjectSection.php index cf4893f3..fafc9509 100644 --- a/src/Debug/Dump/Html/AbstractObjectSection.php +++ b/src/Debug/Dump/Html/AbstractObjectSection.php @@ -13,6 +13,7 @@ namespace bdk\Debug\Dump\Html; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; use bdk\Debug\Dump\Html\Helper; use bdk\Debug\Dump\Html\Value as ValDumper; @@ -52,20 +53,69 @@ public function __construct(ValDumper $valDumper, Helper $helper, HtmlUtil $html */ public function dumpItems(ObjectAbstraction $abs, $what, array $cfg) { - $html = ''; $items = $abs->sort($abs[$what], $abs['sort']); - foreach ($items as $name => $info) { - $info = \array_merge(array( - 'className' => $abs['className'], - 'declaredLast' => null, - 'declaredPrev' => null, - ), $info); - $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; - $html .= $this->dumpItem($name, $info, $cfg) . "\n"; + $cfg = \array_merge(array( + 'groupByInheritance' => \strpos($abs['sort'], 'inheritance') === 0, + 'objClassName' => $abs['className'], + 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, + ), $cfg); + if ($cfg['groupByInheritance'] === false) { + return $this->dumpItemsFiltered($items, $cfg); + } + // group by inheritance... with headings + // stop looping over classes when we've output everything + // no sense in showing "inherited from" when no more inherited items + // Or, we could only display the heading when itemsFiltered non-empty + $classes = $this->getInheritedClasses($abs, $what); + \array_unshift($classes, $abs['className']); + $html = ''; + $itemCount = \count($items); + $itemOutCount = 0; + while ($classes && $itemOutCount < $itemCount) { + $classname = \array_shift($classes); + $itemsFiltered = \array_filter($items, static function ($info) use ($classname) { + return !isset($info['declaredLast']) || $info['declaredLast'] === $classname; + }); + $items = \array_diff_key($items, $itemsFiltered); + $itemOutCount += \count($itemsFiltered); + $html .= \in_array($classname, array($abs['className'], 'stdClass'), true) === false + ? '
Inherited from ' . $this->valDumper->markupIdentifier($classname) . '
' . "\n" + : ''; + $html .= $this->dumpItemsFiltered($itemsFiltered, $cfg); } return $html; } + /** + * Get the extended classes we'll iterate over for "groupByInheritance" + * + * @param ObjectAbstraction $abs Object abstraction + * @param string $what 'cases', 'constants', 'properties', or 'methods' + * + * @return array + */ + private function getInheritedClasses(ObjectAbstraction $abs, $what) + { + $classes = $abs['extends']; + if ($what !== 'constants') { + return $classes; + } + // constants can be defined in interface + $implements = $abs['implements']; + $implementsList = array(); + while ($implements) { + $key = \key($implements); + $val = \array_shift($implements); + if (\is_array($val)) { + $implementsList[] = $key; + \array_splice($implements, 0, 0, $val); + continue; + } + $implementsList[] = $val; + } + return \array_merge($classes, $implementsList); + } + /** * Dump Property or Constant info as HTML * @@ -77,11 +127,6 @@ public function dumpItems(ObjectAbstraction $abs, $what, array $cfg) */ protected function dumpItem($name, array $info, array $cfg) { - $vis = (array) $info['visibility']; - $info['isPrivateAncestor'] = \in_array('private', $vis, true) && $info['isInherited']; - if ($info['isPrivateAncestor']) { - $info['isInherited'] = false; - } return $this->html->buildTag( 'dd', $this->getAttribs($info, $cfg), @@ -94,7 +139,7 @@ protected function dumpItem($name, array $info, array $cfg) * * @param string $name Property name * @param array $info Property info - * @param array $cfg options (currently just attributeOutput) + * @param array $cfg options * * @return string html fragment */ @@ -123,6 +168,34 @@ protected function dumpItemInner($name, array $info, array $cfg) return \implode(' ', $parts); } + /** + * Iterate over cases, constants, properties, or methods + * + * @param array $items Cases, Constants, Properties, or Methods + * @param array $cfg config options + * + * @return string + */ + private function dumpItemsFiltered(array $items, array $cfg) + { + $html = ''; + foreach ($items as $name => $info) { + $vis = (array) $info['visibility']; + $info = \array_merge(array( + 'declaredLast' => null, + 'declaredPrev' => null, + 'objClassName' => $cfg['objClassName'], // used by Properties to determine "isDynamic" + ), $info); + $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $info['objClassName']; + $info['isPrivateAncestor'] = \in_array('private', $vis, true) && $info['isInherited']; + if ($info['isPrivateAncestor']) { + $info['isInherited'] = false; + } + $html .= $this->dumpItem($name, $info, $cfg) . "\n"; + } + return $html; + } + /** * Dump "modifiers" * diff --git a/src/Debug/Dump/Html/HtmlObject.php b/src/Debug/Dump/Html/HtmlObject.php index cd535bf3..0c70705b 100644 --- a/src/Debug/Dump/Html/HtmlObject.php +++ b/src/Debug/Dump/Html/HtmlObject.php @@ -32,6 +32,7 @@ class HtmlObject protected $html; protected $methods; protected $properties; + protected $sectionCallables; /** * Constructor @@ -49,6 +50,16 @@ public function __construct(ValDumper $valDumper, Helper $helper, HtmlUtil $html $this->constants = new ObjectConstants($valDumper, $helper, $html); $this->methods = new ObjectMethods($valDumper, $helper, $html); $this->properties = new ObjectProperties($valDumper, $helper, $html); + $this->sectionCallables = array( + 'attributes' => array($this, 'dumpAttributes'), + 'cases' => array($this->cases, 'dump'), + 'constants' => array($this->constants, 'dump'), + 'extends' => array($this, 'dumpExtends'), + 'implements' => array($this, 'dumpImplements'), + 'methods' => array($this->methods, 'dump'), + 'phpDoc' => array($this, 'dumpPhpDoc'), + 'properties' => array($this->properties, 'dump'), + ); } /** @@ -71,25 +82,53 @@ public function dump(Abstraction $abs) return $this->dumpToString($abs) . $classname . "\n" . 'NOT INSPECTED'; } - if (($abs['cfgFlags'] & AbstractObject::BRIEF) && \in_array('UnitEnum', $abs['implements'], true)) { + if (($abs['cfgFlags'] & AbstractObject::BRIEF) && \strpos(\json_encode($abs['implements']), '"UnitEnum"') !== false) { return $classname; } + if (\strpos($abs['sort'], 'inheritance') === 0) { + $this->valDumper->setDumpOpt('attribs.class.__push__', 'groupByInheritance'); + } $html = $this->dumpToString($abs) . $classname . "\n" . '
' . "\n" - . $this->dumpModifiers($abs) - . $this->dumpExtends($abs) - . $this->dumpImplements($abs) - . $this->dumpAttributes($abs) - . $this->constants->dump($abs) - . $this->cases->dump($abs) - . $this->properties->dump($abs) - . $this->methods->dump($abs) - . $this->dumpPhpDoc($abs) + . $this->dumpInner($abs) . '
' . "\n"; return $this->cleanup($html); } + /** + * Build implements tree + * + * @param array $implements Implements structure + * @param array $interfacesCollapse Interfaces that should initially be hidden + * + * @return string + */ + private function buildImplementsTree(array $implements, array $interfacesCollapse) + { + $str = '' . "\n"; + return $str; + } + /** * Remove empty tags and unneeded attributes * @@ -110,6 +149,22 @@ private function cleanup($html) return $html; } + /** + * Dump the object's details + * + * @param Abstraction $abs Object Abstraction instance + * + * @return string html fragment + */ + protected function dumpInner(Abstraction $abs) + { + $html = $this->dumpModifiers($abs); + foreach ($abs['sectionOrder'] as $sectionName) { + $html .= $this->sectionCallables[$sectionName]($abs); + } + return $html; + } + /** * Dump object attributes * @@ -175,7 +230,7 @@ protected function dumpClassname(Abstraction $abs) ? \trim($abs['phpDoc']['summary'] . "\n\n" . $abs['phpDoc']['desc']) : null; $title = $title ?: null; - if (\in_array('UnitEnum', $abs['implements'], true)) { + if (\strpos(\json_encode($abs['implements']), '"UnitEnum"') !== false) { return $this->html->buildTag( 'span', \array_filter(array( @@ -214,10 +269,11 @@ protected function dumpExtends(Abstraction $abs) */ protected function dumpImplements(Abstraction $abs) { + if (empty($abs['implements'])) { + return ''; + } return '
implements
' . "\n" - . \implode(\array_map(function ($classname) { - return '
' . $this->valDumper->markupIdentifier($classname) . '
' . "\n"; - }, $abs['implements'])); + . '
' . $this->buildImplementsTree($abs['implements'], $abs['interfacesCollapse']) . '
' . "\n"; } /** diff --git a/src/Debug/Dump/Html/ObjectCases.php b/src/Debug/Dump/Html/ObjectCases.php index 6334af40..caded8f3 100644 --- a/src/Debug/Dump/Html/ObjectCases.php +++ b/src/Debug/Dump/Html/ObjectCases.php @@ -29,14 +29,14 @@ class ObjectCases extends AbstractObjectSection */ public function dump(Abstraction $abs) { - if (\in_array('UnitEnum', $abs['implements'], true) === false) { + if (\strpos(\json_encode($abs['implements']), '"UnitEnum"') === false) { return ''; } $cfg = array( 'attributeOutput' => $abs['cfgFlags'] & AbstractObject::CASE_ATTRIBUTE_OUTPUT, 'collect' => $abs['cfgFlags'] & AbstractObject::CASE_COLLECT, + 'groupByInheritance' => false, 'output' => $abs['cfgFlags'] & AbstractObject::CASE_OUTPUT, - 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, ); if (!$cfg['output']) { return ''; diff --git a/src/Debug/Dump/Html/ObjectConstants.php b/src/Debug/Dump/Html/ObjectConstants.php index fc010c85..99c7b876 100644 --- a/src/Debug/Dump/Html/ObjectConstants.php +++ b/src/Debug/Dump/Html/ObjectConstants.php @@ -33,7 +33,6 @@ public function dump(Abstraction $abs) 'attributeOutput' => $abs['cfgFlags'] & AbstractObject::CONST_ATTRIBUTE_OUTPUT, 'collect' => $abs['cfgFlags'] & AbstractObject::CONST_COLLECT, 'output' => $abs['cfgFlags'] & AbstractObject::CONST_OUTPUT, - 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, ); if (!$cfg['output']) { return ''; @@ -57,9 +56,7 @@ protected function getClasses(array $info) $visClasses = \array_diff((array) $info['visibility'], array('debug')); $classes = \array_keys(\array_filter(array( 'constant' => true, - 'inherited' => $info['isInherited'], 'isFinal' => $info['isFinal'], - 'overrides' => $info['isInherited'] === false && $info['declaredPrev'], 'private-ancestor' => $info['isPrivateAncestor'], ))); return \array_merge($classes, $visClasses); @@ -70,9 +67,8 @@ protected function getClasses(array $info) */ protected function getModifiers(array $info) { - $modifiers = (array) $info['visibility']; - return \array_merge($modifiers, \array_keys(\array_filter(array( + return \array_merge(\array_keys(\array_filter(array( 'final' => $info['isFinal'], - )))); + ))), (array) $info['visibility']); } } diff --git a/src/Debug/Dump/Html/ObjectMethods.php b/src/Debug/Dump/Html/ObjectMethods.php index bc8234c9..59adb00e 100644 --- a/src/Debug/Dump/Html/ObjectMethods.php +++ b/src/Debug/Dump/Html/ObjectMethods.php @@ -39,6 +39,7 @@ public function dump(Abstraction $abs) 'output' => $abs['cfgFlags'] & AbstractObject::METHOD_OUTPUT, 'paramAttributeOutput' => $abs['cfgFlags'] & AbstractObject::PARAM_ATTRIBUTE_OUTPUT, 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, + 'staticVarOutput' => $abs['cfgFlags'] & AbstractObject::METHOD_STATIC_VAR_OUTPUT, ); if (!$this->opts['output']) { // we're not outputting methods @@ -54,6 +55,7 @@ public function dump(Abstraction $abs) return \str_replace(array( ' data-deprecated-desc="null"', ' data-implements="null"', + ' data-throws="null"', ' ', ), '', $html); } @@ -65,18 +67,26 @@ protected function dumpItemInner($name, array $info, array $cfg) { return $this->dumpModifiers($info) . ' ' . $this->dumpName($name, $info) - . $this->dumpParams($info['params']) + . $this->dumpParams($info) . $this->dumpReturnType($info) + . $this->dumpStaticVars($info) . ($name === '__toString' - ? '
' . "\n" . $this->valDumper->dump($info['returnValue']) + ? "\n" . '

return value

' . "\n" + . '' : ''); } /** * Dump method name with phpdoc summary & desc * - * @param string $name method name - * @param array $info method info + * @param string $name Method name + * @param array $info Method info * * @return string html fragment */ @@ -100,15 +110,13 @@ protected function dumpName($name, array $info) /** * Dump method parameters as HTML * - * @param array $params Params as returned from getParams() + * @param array $info Method info * * @return string html fragment */ - protected function dumpParams(array $params) + protected function dumpParams(array $info) { - foreach ($params as $i => $info) { - $params[$i] = $this->dumpParam($info); - } + $params = \array_map(array($this, 'dumpParam'), $info['params']); return '(' . \implode(', ', $params) . ')'; @@ -191,7 +199,7 @@ protected function dumpParamName(array $info) * * @return string */ - protected function dumpReturnType($info) + protected function dumpReturnType(array $info) { if ($info['return']['type'] === null) { return ''; @@ -204,6 +212,35 @@ protected function dumpReturnType($info) )); } + /** + * Dump method's return type + * + * @param array $info Method info + * + * @return string + */ + protected function dumpStaticVars(array $info) + { + if (!$this->opts['staticVarOutput'] || empty($info['staticVars'])) { + return ''; + } + $html = "\n" . '

static variables

' . "\n"; + $html .= ''; + return $html; + } + /** * {@inheritDoc} */ @@ -214,6 +251,9 @@ protected function getAttribs(array $info, array $cfg = array()) ? $info['phpDoc']['deprecated'][0]['desc'] : null, 'data-implements' => $info['implements'], + 'data-throws' => $this->opts['phpDocOutput'] && isset($info['phpDoc']['throws']) + ? $info['phpDoc']['throws'] + : null, )); } @@ -224,12 +264,10 @@ protected function getClasses(array $info) { $visClasses = \array_diff((array) $info['visibility'], array('debug')); $classes = \array_keys(\array_filter(array( - 'inherited' => $info['isInherited'], 'isDeprecated' => $info['isDeprecated'], 'isFinal' => $info['isFinal'], 'isStatic' => $info['isStatic'], 'method' => true, - 'overrides' => $info['isInherited'] === false && $info['declaredPrev'], ))); return \array_merge($classes, $visClasses); } @@ -260,7 +298,7 @@ protected function getModifiers(array $info) // phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder return \array_keys(\array_filter(array( 'final' => $info['isFinal'], - $info['visibility'] => true, + \implode(' ', (array) $info['visibility']) => true, 'static' => $info['isStatic'], ))); } diff --git a/src/Debug/Dump/Html/ObjectProperties.php b/src/Debug/Dump/Html/ObjectProperties.php index bf4e3e8c..fb795075 100644 --- a/src/Debug/Dump/Html/ObjectProperties.php +++ b/src/Debug/Dump/Html/ObjectProperties.php @@ -31,7 +31,6 @@ public function dump(Abstraction $abs) { $cfg = array( 'attributeOutput' => $abs['cfgFlags'] & AbstractObject::PROP_ATTRIBUTE_OUTPUT, - 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, ); $magicMethods = \array_intersect(array('__get', '__set'), \array_keys($abs['methods'])); $html = '
' . $this->getLabel($abs) . '
' . "\n"; @@ -51,14 +50,12 @@ protected function getClasses(array $info) 'debuginfo-excluded' => $info['debugInfoExcluded'], 'debuginfo-value' => $info['valueFrom'] === 'debugInfo', 'forceShow' => $info['forceShow'], - 'inherited' => $info['isInherited'], 'isDynamic' => $info['declaredLast'] === null && $info['valueFrom'] === 'value' - && $info['className'] !== 'stdClass', + && $info['objClassName'] !== 'stdClass', 'isPromoted' => $info['isPromoted'], 'isReadOnly' => $info['isReadOnly'], 'isStatic' => $info['isStatic'], - 'overrides' => $info['isInherited'] === false && $info['declaredPrev'], 'private-ancestor' => $info['isPrivateAncestor'], 'property' => true, ))); diff --git a/src/Debug/Dump/TextAnsiValue.php b/src/Debug/Dump/TextAnsiValue.php index d5d14bc4..1fcdf6a8 100644 --- a/src/Debug/Dump/TextAnsiValue.php +++ b/src/Debug/Dump/TextAnsiValue.php @@ -254,21 +254,11 @@ protected function dumpObjectProperties(Abstraction $abs) */ protected function dumpPropPrefix(array $info) { - $info = \array_filter(array( - 'inherited' => $info['isInherited'], - 'isDynamic' => $info['declaredLast'] === null - && $info['valueFrom'] === 'value' - && $info['className'] !== 'stdClass', - 'overrides' => $info['isInherited'] === false && $info['declaredPrev'], + return \strtr(parent::dumpPropPrefix($info), array( + '↳' => $this->cfg['escapeCodes']['muted'] . '↳' . $this->escapeReset, + '⚠' => $this->cfg['escapeCodesMethods']['warn'] . '⚠' . $this->escapeReset, + '⟳' => $this->cfg['escapeCodes']['muted'] . '⟳' . $this->escapeReset, )); - $prefixes = \array_intersect_key(array( - 'inherited' => $this->cfg['escapeCodes']['muted'] . '↳' . $this->escapeReset, - 'isDynamic' => $this->cfg['escapeCodesMethods']['warn'] . '⚠' . $this->escapeReset, - 'overrides' => $this->cfg['escapeCodes']['muted'] . '⟳' . $this->escapeReset, - ), $info); - return $prefixes - ? \implode(' ', $prefixes) . ' ' - : ''; } /** diff --git a/src/Debug/Plugin/InternalEvents.php b/src/Debug/Plugin/InternalEvents.php index dbc6a94d..0df81f02 100644 --- a/src/Debug/Plugin/InternalEvents.php +++ b/src/Debug/Plugin/InternalEvents.php @@ -275,7 +275,7 @@ private function logError(Error $error) $this->debug->rootInstance->getChannel('phpError')->{$method}( $error['typeStr'] . ':', $error['message'], - \sprintf('%s (line %s)', $error['file'], $error['line']), + $error['fileAndLine'], $this->debug->meta(array( 'context' => $error['category'] === 'fatal' && $error['backtrace'] === null ? $error['context'] @@ -283,6 +283,7 @@ private function logError(Error $error) 'errorCat' => $error['category'], 'errorHash' => $error['hash'], 'errorType' => $error['type'], + 'evalLine' => $error['evalLine'], 'file' => $error['file'], 'isSuppressed' => $error['isSuppressed'], // set via event subscriber vs "@"" code prefix 'line' => $error['line'], diff --git a/src/Debug/Plugin/Method/Basic.php b/src/Debug/Plugin/Method/Basic.php index 9e3c2d91..1b47b4fb 100644 --- a/src/Debug/Plugin/Method/Basic.php +++ b/src/Debug/Plugin/Method/Basic.php @@ -64,9 +64,11 @@ public function assert($assertion, $msg = null) if (!$args) { // add default message $callerInfo = $this->debug->backtrace->getCallerInfo(); + $fileAndLine = \sprintf('%s (line %s, eval\'d line %s)', $callerInfo['file'], $callerInfo['line'], $callerInfo['evalLine']); + $fileAndLine = \str_replace(', eval\'d line )', ')', $fileAndLine); $args = array( 'Assertion failed:', - \sprintf('%s (line %s)', $callerInfo['file'], $callerInfo['line']), + $fileAndLine, ); $logEntry->setMeta('detectFiles', true); } @@ -258,23 +260,27 @@ protected function appendLog(LogEntry $logEntry) */ protected function doError($method, $args) { + $default = "\x00default\x00"; $logEntry = new LogEntry( $this->debug, $method, $args, array( 'detectFiles' => true, + 'evalLine' => null, + 'file' => $default, + 'line' => null, 'uncollapse' => true, ) ); // file & line meta may -already be set (ie coming via errorHandler) // file & line may also be defined as null - $default = "\x00default\x00"; if ($logEntry->getMeta('file', $default) === $default) { $callerInfo = $this->debug->backtrace->getCallerInfo(); $logEntry->setMeta(array( + 'evalLine' => $callerInfo['evalLine'], 'file' => $callerInfo['file'], - 'line' => $callerInfo['evalLine'] ?: $callerInfo['line'], + 'line' => $callerInfo['line'], )); } $this->appendLog($logEntry); diff --git a/src/Debug/Plugin/Method/Clear.php b/src/Debug/Plugin/Method/Clear.php index cf5dce22..7ad2e333 100644 --- a/src/Debug/Plugin/Method/Clear.php +++ b/src/Debug/Plugin/Method/Clear.php @@ -346,6 +346,7 @@ private function updateLogEntry(LogEntry $logEntry, $args) 'args' => $args, 'meta' => \array_merge(array( 'bitmask' => $bitmask, + 'evalLine' => $callerInfo['evalLine'], 'file' => $callerInfo['file'], 'flags' => array( 'alerts' => (bool) ($bitmask & Debug::CLEAR_ALERTS), diff --git a/src/Debug/Plugin/Method/Count.php b/src/Debug/Plugin/Method/Count.php index 6d2ad03b..d185cc84 100644 --- a/src/Debug/Plugin/Method/Count.php +++ b/src/Debug/Plugin/Method/Count.php @@ -102,6 +102,7 @@ private function doCount(LogEntry $logEntry) // determine dataLabel from calling file & line $callerInfo = $debug->backtrace->getCallerInfo(); $logEntry['meta'] = \array_merge(array( + 'evalLine' => $callerInfo['evalLine'], 'file' => $callerInfo['file'], 'line' => $callerInfo['line'], ), $logEntry['meta']); diff --git a/src/Debug/Plugin/Method/General.php b/src/Debug/Plugin/Method/General.php index 249bce39..6d200e94 100644 --- a/src/Debug/Plugin/Method/General.php +++ b/src/Debug/Plugin/Method/General.php @@ -187,8 +187,9 @@ public function setErrorCaller($caller = null) if ($caller === null) { $caller = $this->debug->backtrace->getCallerInfo(1); $caller = array( + 'evalLine' => $caller['evalLine'], 'file' => $caller['file'], - 'line' => $caller['evalLine'] ?: $caller['line'], + 'line' => $caller['line'], ); } if ($caller) { diff --git a/src/Debug/Plugin/Method/Output.php b/src/Debug/Plugin/Method/Output.php index 1fdd1418..c2f1d5fb 100644 --- a/src/Debug/Plugin/Method/Output.php +++ b/src/Debug/Plugin/Method/Output.php @@ -58,13 +58,13 @@ public function output($cfg = array()) $debug->obEnd(); return null; } - $output = $this->publishOutputEvent(); + $event = $this->publishOutputEvent(); if (!$debug->parentInstance) { $debug->data->set('outputSent', true); } $debug->config->set($cfgRestore); $debug->obEnd(); - return $output; + return $event['return']; } /** @@ -74,7 +74,7 @@ public function output($cfg = array()) * finally ourself * This isn't outputing each channel, but for performing any per-channel "before output" activities * - * @return string output + * @return \bdk\PubSub\Event */ private function publishOutputEvent() { @@ -98,6 +98,6 @@ private function publishOutputEvent() ) ); } - return $event['return']; + return $event; } } diff --git a/src/Debug/Route/Discord.php b/src/Debug/Route/Discord.php index 3ac378f6..2dc37e72 100644 --- a/src/Debug/Route/Discord.php +++ b/src/Debug/Route/Discord.php @@ -120,7 +120,7 @@ private function buildMessage(Error $error) . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()) ) . "\n" . $error->getMessageText() . "\n" - . $error['file'] . ' (line ' . $error['line'] . ')', + . $error['fileAndLine'], ); } diff --git a/src/Debug/Route/Html.php b/src/Debug/Route/Html.php index 65971194..84955b59 100644 --- a/src/Debug/Route/Html.php +++ b/src/Debug/Route/Html.php @@ -46,7 +46,7 @@ public function __construct(Debug $debug) 'drawer' => true, 'filepathCss' => __DIR__ . '/../css/Debug.css', 'filepathScript' => __DIR__ . '/../js/Debug.jquery.min.js', - 'jqueryUrl' => '//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js', + 'jqueryUrl' => '//ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js', 'outputCss' => true, 'outputScript' => true, 'sidebar' => true, diff --git a/src/Debug/Route/Html/ErrorSummary.php b/src/Debug/Route/Html/ErrorSummary.php index 34275f9a..857edf86 100644 --- a/src/Debug/Route/Html/ErrorSummary.php +++ b/src/Debug/Route/Html/ErrorSummary.php @@ -203,7 +203,7 @@ private function buildFatalContext(Error $error) 'data-file' => $error['file'], 'data-line' => $error['line'], ), - \sprintf('%s (line %s)', $error['file'], $error['line']) + $error['fileAndLine'] ) . "\n"; if ($context) { $return .= '
  • ' @@ -338,11 +338,10 @@ protected function buildNotInConsole() . ' +
  • +
    +
    diff --git a/tests/Debug/Plugin/Method/TableTest.php b/tests/Debug/Plugin/Method/TableTest.php index 44a75c95..bc22127c 100644 --- a/tests/Debug/Plugin/Method/TableTest.php +++ b/tests/Debug/Plugin/Method/TableTest.php @@ -466,7 +466,7 @@ public static function providerTestMethod() 12233-03-22T00:00:00%i 2Resource id #%d: stream 3callable bdk\Test\Debug\Plugin\Method\TableTest::providerTestMethod - 4Closure + 4Closure
    modifiers
    final
    diff --git a/tests/Debug/Plugin/Method/TraceTest.php b/tests/Debug/Plugin/Method/TraceTest.php index f54f3e7c..fd726d2c 100644 --- a/tests/Debug/Plugin/Method/TraceTest.php +++ b/tests/Debug/Plugin/Method/TraceTest.php @@ -172,6 +172,7 @@ public function testTraceInvalidCaption() 'args' => array('trace caption should be a string. bool provided'), 'meta' => array( 'detectFiles' => true, + 'evalLine' => null, 'file' => __FILE__, 'line' => $line, 'uncollapse' => true, @@ -189,6 +190,7 @@ public function testTraceInvalidCaption() 'args' => array('trace caption should be a string. stdClass provided'), 'meta' => array( 'detectFiles' => true, + 'evalLine' => null, 'file' => __FILE__, 'line' => $line, 'uncollapse' => true, diff --git a/tests/Debug/Plugin/RedactionTest.php b/tests/Debug/Plugin/RedactionTest.php index 6aebc4af..e0efd28f 100644 --- a/tests/Debug/Plugin/RedactionTest.php +++ b/tests/Debug/Plugin/RedactionTest.php @@ -173,7 +173,8 @@ public static function providerTestMethod() 'entry' => static function (LogEntry $logEntry) { $logEntry = \bdk\Test\Debug\Helper::logEntryToArray($logEntry); $obj = $logEntry['args'][0]; - self::assertSame(null, $obj['stringified']); + // self::assertSame(null, $obj['stringified']); + self::assertArrayNotHasKey('stringified', $obj); self::assertSame('foo=bar&password=█████████&ding=dong', $obj['methods']['__toString']['returnValue']); }, ), diff --git a/tests/Debug/Route/HtmlTest.php b/tests/Debug/Route/HtmlTest.php index 7d52a6f9..97dc41f5 100644 --- a/tests/Debug/Route/HtmlTest.php +++ b/tests/Debug/Route/HtmlTest.php @@ -34,6 +34,7 @@ public function testOutput()
  • Built In %f %s
  • Peak Memory Usage ?⃝: %s / %s
  • %A +
    diff --git a/tests/Debug/Route/WampTest.php b/tests/Debug/Route/WampTest.php index b7660264..333535a9 100644 --- a/tests/Debug/Route/WampTest.php +++ b/tests/Debug/Route/WampTest.php @@ -52,6 +52,7 @@ public function testErrorWithTrace() array( 'caption' => 'trace', 'detectFiles' => true, + 'evalLine' => null, 'file' => $this->file, 'foundFiles' => array( __FILE__, diff --git a/tests/Debug/SubstitutionTest.php b/tests/Debug/SubstitutionTest.php index 9c9fc309..6ff37ad4 100644 --- a/tests/Debug/SubstitutionTest.php +++ b/tests/Debug/SubstitutionTest.php @@ -395,6 +395,7 @@ public function doTestSubstitution($method, $args, $tests) } } elseif (\in_array($method, array('error','warn'))) { $test['meta']['detectFiles'] = true; + $test['meta']['evalLine'] = null; $test['meta']['file'] = $this->file; $test['meta']['line'] = $this->line; $test['meta']['uncollapse'] = true; diff --git a/tests/Debug/Type/ArrayTest.php b/tests/Debug/Type/ArrayTest.php index a057960e..93f1bf64 100644 --- a/tests/Debug/Type/ArrayTest.php +++ b/tests/Debug/Type/ArrayTest.php @@ -30,7 +30,7 @@ public static function providerTestMethod()
  • 0=>a
  • foo=>bar
  • 1=>c
  • -
  • obj=>
    stdClass +
  • obj=>
    stdClass
    ' . (PHP_VERSION_ID >= 80200 ? '
    attributes
    diff --git a/tests/Debug/Type/BasicTest.php b/tests/Debug/Type/BasicTest.php index 59780760..067bc0fb 100644 --- a/tests/Debug/Type/BasicTest.php +++ b/tests/Debug/Type/BasicTest.php @@ -11,7 +11,6 @@ * * @covers \bdk\Debug\Abstraction\Abstracter * @covers \bdk\Debug\Abstraction\AbstractString - * @covers \bdk\Debug\Abstraction\Object\Subscriber * @covers \bdk\Debug\Dump\BaseValue * @covers \bdk\Debug\Dump\Html * @covers \bdk\Debug\Dump\Html\HtmlString @@ -141,66 +140,6 @@ public static function providerTestMethod() ), ), - 'closure' => array( - 'log', - array( - static function ($foo, $bar) { - return $foo . $bar; - }, - ), - array( - 'entry' => static function ($logEntry) { - $objAbs = $logEntry['args'][0]; - self::assertAbstractionType($objAbs); - $values = $objAbs->getValues(); - self::assertSame('Closure', $values['className']); - $line = __LINE__ - 10; - self::assertSame(array( - 'extensionName' => false, - 'fileName' => __FILE__, - 'startLine' => $line, - ), $values['definition']); - \array_walk($values['properties'], static function ($propInfo, $propName) use ($line) { - // echo \json_encode($propInfo, JSON_PRETTY_PRINT); - $values = \array_intersect_key($propInfo, \array_flip(array( - 'value', - 'valueFrom', - 'visibility', - ))); - switch ($propName) { - case 'debug.file': - self::assertSame(array( - 'value' => __FILE__, - 'valueFrom' => 'debug', - 'visibility' => 'debug', - ), $values); - break; - case 'debug.line': - self::assertSame(array( - 'value' => $line, - 'valueFrom' => 'debug', - 'visibility' => 'debug', - ), $values); - break; - } - }); - }, - 'html' => '
  • Closure -
    -
    modifiers
    -
    final
    -
    properties
    -
    debug string file = ' . __FILE__ . '
    -
    debug int line = %d
    -
    methods
    -
    private __construct()
    -
    public __invoke($foo, $bar)
    - %a -
    -
  • ', - ), - ), - 'float' => array( 'log', array(10.10), diff --git a/tests/Debug/Type/EnumTest.php b/tests/Debug/Type/EnumTest.php index fae4013d..8255f719 100644 --- a/tests/Debug/Type/EnumTest.php +++ b/tests/Debug/Type/EnumTest.php @@ -72,15 +72,18 @@ public static function providerTestMethod() ), ), $cases); }, - 'html' => '
  • bdk\Test\Debug\Fixture\Enum\Meals::BREAKFAST
    modifiers
    final
    -
    implements
    -
    UnitEnum
    attributes
    bdk\Test\Debug\Fixture\Enum\ExampleAttribute
    +
    implements
    +
      +
    • UnitEnum
    • +
    +
    constants
    public ENUM_VALUE =
    bdk\Test\Debug\Fixture\Enum\Meals::DINNER
    @@ -171,13 +174,19 @@ public static function providerTestMethod() ), ), $cases); }, - 'html' => '
  • bdk\Test\Debug\Fixture\Enum\MealsBacked::BREAKFAST + 'html' => '
  • bdk\Test\Debug\Fixture\Enum\MealsBacked::BREAKFAST
    modifiers
    final
    implements
    -
    BackedEnum
    -
    UnitEnum
    +
      +
    • BackedEnum +
        +
      • UnitEnum
      • +
      +
    • +
    +
    constants
    public ENUM_VALUE =
    bdk\Test\Debug\Fixture\Enum\MealsBacked::DINNER
    public REGULAR_CONSTANT = test
    diff --git a/tests/Debug/Type/ObjectTest.php b/tests/Debug/Type/ObjectTest.php index 4b0aa1bc..5a897d23 100644 --- a/tests/Debug/Type/ObjectTest.php +++ b/tests/Debug/Type/ObjectTest.php @@ -9,6 +9,7 @@ use bdk\Debug\LogEntry; use bdk\Test\Debug\DebugTestFramework; use bdk\Test\Debug\Fixture\TestObj; +use ReflectionObject; /** * PHPUnit tests for Debug class @@ -42,9 +43,13 @@ * @covers \bdk\Debug\Dump\TextAnsi * @covers \bdk\Debug\Dump\TextAnsiValue * @covers \bdk\Debug\Dump\TextValue + * + * @phpcs:disable Generic.Arrays.ArrayIndent.KeyIncorrect */ class ObjectTest extends DebugTestFramework { + protected static $testObj; + public static function providerTestMethod() { $text = <<<'EOD' @@ -133,7 +138,9 @@ public static function providerTestMethod() $crate = new \bdk\Debug\Route\WampCrate(Debug::getInstance()); - $abs1 = Debug::getInstance()->abstracter->getAbstraction(new TestObj(), 'log'); + self::$testObj = new TestObj(); + self::$testObj->methodPublic((object) array()); + $abs1 = Debug::getInstance()->abstracter->getAbstraction(self::$testObj, 'log'); $cratedAbs1 = $crate->crate($abs1); $cratedAbs1 = \bdk\Test\Debug\Helper::crate($cratedAbs1); // as provider method is static, but test is not static... @@ -145,20 +152,86 @@ public static function providerTestMethod() $cratedAbs2 = \bdk\Test\Debug\Helper::crate($cratedAbs2); $cratedAbs2['scopeClass'] = __CLASS__; - return array( + $tests = array( + 'closure' => array( + 'log', + array( + static function ($foo, $bar) { + return $foo . $bar; + }, + ), + array( + 'entry' => static function ($logEntry) { + $objAbs = $logEntry['args'][0]; + self::assertAbstractionType($objAbs); + $values = $objAbs->getValues(); + self::assertSame('Closure', $values['className']); + $line = __LINE__ - 10; + self::assertSame(array( + 'extensionName' => false, + 'fileName' => __FILE__, + 'startLine' => $line, + ), $values['definition']); + \array_walk($values['properties'], static function ($propInfo, $propName) use ($line) { + // echo \json_encode($propInfo, JSON_PRETTY_PRINT); + $values = \array_intersect_key($propInfo, \array_flip(array( + 'value', + 'valueFrom', + 'visibility', + ))); + switch ($propName) { + case 'debug.file': + self::assertSame(array( + 'value' => __FILE__, + 'valueFrom' => 'debug', + 'visibility' => 'debug', + ), $values); + break; + case 'debug.line': + self::assertSame(array( + 'value' => $line, + 'valueFrom' => 'debug', + 'visibility' => 'debug', + ), $values); + break; + } + }); + }, + 'html' => '
  • Closure +
    +
    modifiers
    +
    final
    +
    properties
    +
    debug string file = ' . __FILE__ . '
    +
    debug int line = %d
    +
    methods
    +
    private __construct()
    +
    public __invoke($foo, $bar)
    + %a +
    +
  • ', + ), + ), + 'testObj' => array( 'log', array( - new TestObj(), + self::$testObj, ), array( 'entry' => static function (LogEntry $logEntry) { $objAbs = $logEntry['args'][0]; self::assertAbstractionType($objAbs); + self::assertSame(array( + array( + 'desc' => 'when toStrThrow is `1`', + 'type' => 'Exception', + ), + ), $objAbs['methods']['__toString']['phpDoc']['throws']); }, 'html' => static function ($str) { self::assertStringStartsWith( - '
  • ' + '
  • ' . 'abracadabra' . "\n" . 'bdk\Test\Debug\Fixture\TestBase', $str); // implements - if (\defined('HHVM_VERSION')) { + if (PHP_VERSION_ID >= 80000) { self::assertStringContainsString(\implode("\n", array( '
    implements
    ', - '
    Stringish
    ', - '
    XHPChild
    ', - )), $str); - } elseif (PHP_VERSION_ID >= 80000) { - self::assertStringContainsString(\implode("\n", array( - '
    implements
    ', - '
    Stringable
    ', + '
      ', + '
    • Stringable
    • ', + '
    ', + '
    ', )), $str); } else { self::assertStringNotContainsString('
    implements
    ', $str); @@ -192,8 +262,9 @@ public static function providerTestMethod() // constants $expect = PHP_VERSION_ID >= 70100 ? '
    constants
    ' . "\n" - . '
    public INHERITED = defined in TestBase
    ' . "\n" - . '
    public MY_CONSTANT = redefined in Test
    ' + . '
    public MY_CONSTANT = redefined in Test
    ' . "\n" + . '
    Inherited from bdk\Test\Debug\Fixture\TestBase
    ' . "\n" + . '
    public INHERITED = defined in TestBase
    ' . "\n" : '
    constants
    ' . "\n" . '
    public INHERITED = defined in TestBase
    ' . "\n" . '
    public MY_CONSTANT = redefined in Test
    '; @@ -205,7 +276,7 @@ public static function providerTestMethod() '
    This object has __get and __set methods
    ', '
    public baseDynamic = duo
    ', '
    public dynamic = dynomite!
    ', - '
    public propPublic = redefined in Test (public)
    ', + '
    public propPublic = redefined in Test (public)
    ', '
    public static propStatic = I\'m Static
    ', '
    public someArray = array(', '
      ', @@ -213,7 +284,7 @@ public static function providerTestMethod() "\t" . '
    • numeric=>123
    • ', "\t" . '
    • string=>cheese
    • ', "\t" . '
    • bool=>true
    • ', - "\t" . '
    • obj=>
      stdClass', + "\t" . '
    • obj=>
      stdClass', (PHP_VERSION_ID >= 80200 ? '
      ' . "\n" . '
      attributes
      ' . "\n" @@ -225,9 +296,6 @@ public static function providerTestMethod() '
      ', '
    • ', '
    )
    ', - '
    magic bool magicProp
    ', - '
    magic-read protected bool magicReadProp = not null
    ', - '
    protected propProtected = defined only in TestBase (protected)
    ', '
    private debug =
    bdk\Debug', 'NOT INSPECTED
    ', '
    private instance =
    bdk\Test\Debug\Fixture\TestObj', '*RECURSION*
    ', '
    private propNoDebug = not included in __debugInfo
    ', - '
    private string propPrivate = redefined in Test (private) (alternate value via __debugInfo)
    ', - '
    private string testBasePrivate = defined in TestBase (private)
    ', + '
    private string propPrivate = redefined in Test (private) (alternate value via __debugInfo)
    ', '
    private toString = abracadabra
    ', '
    private toStrThrow = 0
    ', '
    debug debugValue = This property is debug only
    ', + '
    Inherited from bdk\Test\Debug\Fixture\TestBase
    ', + '
    magic bool magicProp
    ', + '
    magic-read protected bool magicReadProp = not null
    ', + '
    protected propProtected = defined only in TestBase (protected)
    ', + '
    private string testBasePrivate = defined in TestBase (private)
    ', '
    methods
    ', )); if (PHP_VERSION_ID >= 80100) { @@ -253,29 +325,45 @@ public static function providerTestMethod() $expect = \implode("\n", array( '
    methods
    ', '
    This object has a __call method
    ', - '
    public public __construct(string $toString = abracadabra, int $toStrThrow = 0)
    ', - '
    public __call(string $name, array $args): mixed
    ', + 'Constructor description">__construct
    (string $toString = abracadabra, int $toStrThrow = 0)', '
    public __debugInfo(): array
    ', - '
    public __get(string $key): mixed
    ', - '
    public __set(string $key, mixed $val): void
    ', - '
    public __toString(): string
    ', - 'abracadabra
    ', - '
    final public methodPublic(SomeClass $param1, array $param2 = array()): void
    ', - '
    public testBasePublic()
    ', - '
    public static testBaseStatic()
    ', - '
    magic presto($foo, int $int = 1, $bool = true, $null = null): void
    ', - '
    magic static prestoStatic(string $noDefault, $arr = array(), $opts = array(\'a\'=>\'ay\',\'b\'=>\'bee\'), $val = self::MY_CONSTANT): void
    ', + '
    = 80000 ? 'data-implements="Stringable" ' : '' ) . 'data-throws="[{"desc":"when toStrThrow is `1`","type":"Exception"}]">public __toString(): string', + '

    static variables

    ', + '
      ', + '
    • static= I'm static
    • ', + '
    ', + '

    return value

    ', + '
    • abracadabra
    ', + '
    final public methodPublic(stdClass $param1, array $param2 = array()): void', + '

    static variables

    ', + '
      ', + '
    • foo= 42
    • ', + '
    • bar= test
    • ', + '
    • baz=
      bdk\Test\Debug\Fixture\TestObj', + '*RECURSION*
    • ', + '
    ', '
    protected methodProtected(bdk\Debug\Abstraction\Abstraction[] $param1): void
    ', '
    private methodPrivate(mixed &$param1, bdk\PubSub\Event[] &$param2, bool ...$param3): void
    ', + '
    Inherited from bdk\Test\Debug\Fixture\TestBase
    ', + '
    public __call(string $name, array $args): mixed
    ', + '
    public __get(string $key): mixed
    ', + '
    public __set(string $key, mixed $val): void
    ', + '
    public testBasePublic()
    ', + '
    public static testBaseStatic()
    ', + '
    magic presto($foo, int $int = 1, $bool = true, $null = null): void
    ', + '
    magic static prestoStatic(string $noDefault, $arr = array(), $opts = array('a'=>'ay','b'=>'bee'), $val = self::MY_CONSTANT): void
    ', '
    phpDoc
    ', )); - if (PHP_VERSION_ID >= 80100) { - $expect = \str_replace('\'', ''', $expect); + if (PHP_VERSION_ID <= 80100) { + $expect = \str_replace(''', '\'', $expect); } - // echo 'expect = ' . $expect . "\n\n"; // echo 'actual = ' . $str . "\n"; self::assertStringContainsString($expect, $str); @@ -321,8 +409,9 @@ public static function providerTestMethod() $expect = \implode("\n", array( '
    properties
    ', '
    This object has a __get method
    ', - '
    magic bool magicProp
    ', - '
    magic-read protected bool magicReadProp = not null
    ', + '
    Inherited from bdk\Test\Debug\Fixture\Test2Base
    ', + '
    magic bool magicProp
    ', + '
    magic-read protected bool magicReadProp = not null
    ', )); if (PHP_VERSION_ID >= 80100) { $expect = \str_replace('\'', ''', $expect); @@ -339,18 +428,18 @@ public static function providerTestMethod() $expect = \implode("\n", array( '
    methods
    ', '
    This object has a __call method
    ', - '
    public __call(string $name, array $args): mixed
    ', - '
    public __get(string $key): mixed
    ', + '
    Inherited from bdk\Test\Debug\Fixture\Test2Base
    ', + '
    public __call(string $name, array $args): mixed
    ', + '
    public __get(string $key): mixed
    ', \version_compare(PHP_VERSION, '5.4.6', '>=') - ? '
    public constDefault(string $param = ' . $constName . '): void
    ' - : '
    public constDefault(string $param = bird): void
    ', - '
    magic methConstTest($mode = self::WORD)
    ', + ? '
    public constDefault(string $param = ' . $constName . '): void
    ' + : '
    public constDefault(string $param = bird): void
    ', + '
    magic methConstTest($mode = self::WORD)
    ', '
  • ', )); if (PHP_VERSION_ID >= 80100) { $expect = \str_replace('\'', ''', $expect); } - // echo 'expect = ' . $expect . "\n"; // echo 'str = ' . $str . "\n"; self::assertStringContainsString($expect, $html); @@ -485,19 +574,21 @@ public static function providerTestMethod() 'html' => static function ($html) { $expect = '
    constants
    ' . "\n" . (PHP_VERSION_ID >= 70100 - ? '
    public SOME_CONSTANT = never change
    ' + ? '
    public SOME_CONSTANT = never change
    ' : '
    public SOME_CONSTANT = never change
    ' ) . "\n" + // . '
    Inherited from bdk\Test\Debug\Fixture\Utility\PhpDocImplements
    ' . "\n" . '
    properties
    ' . "\n" - . '
    public string someProperty = St. James Place
    ' . "\n" + . '
    public string someProperty = St. James Place
    ' . "\n" . '
    magic bool magicProp
    ' . "\n" . '
    magic-read bool magicReadProp
    ' . "\n" + // . '
    Inherited from bdk\Test\Debug\Fixture\Utility\PhpDocImplements
    ' . "\n" . '
    methods
    ' . "\n" - . '
    public public someMethod(): bdk\Test\Debug\Fixture\SomeInterface
    ' . "\n" - . '
    public someMethod2(): void
    ' . "\n" - . '
    public public someMethod2(): void
    ' . "\n" + . '
    public someMethod3()
    '; if (PHP_VERSION_ID >= 80100) { @@ -519,7 +610,7 @@ public static function providerTestMethod() 'html' => static function ($html) { // echo 'html = ' . $html . "\n"; $expect = \trim(\preg_replace('/^\s+/m', '', ' -
  • bdk\Test\Debug\Fixture\ArrayDocs +
  • bdk\Test\Debug\Fixture\ArrayDocs
    properties
    public non-empty-array<string, array<int, int|string>|int|string>[] general = null
    @@ -612,7 +703,7 @@ public static function providerTestMethod() ), ), array( - 'html' => '
  • stdClass + 'html' => '
  • stdClass
    %A
    properties
    ' . (PHP_VERSION_ID >= 70400 ? '
    public = empty
    ' . "\n" : '') @@ -636,6 +727,10 @@ public static function providerTestMethod() ), ), ); + + // $tests = \array_intersect_key($tests, \array_flip(array('testObj'))); + + return $tests; } /** @@ -668,8 +763,7 @@ public function testAbstraction() { // mostly tested via logTest, infoTest, warnTest, errorTest.... // test object inheritance - $test = new TestObj(); - $abs = $this->debug->abstracter->getAbstraction($test); + $abs = $this->debug->abstracter->getAbstraction(self::$testObj); self::assertSame('object', $abs['type']); self::assertSame('bdk\Test\Debug\Fixture\TestObj', $abs['className']); @@ -849,7 +943,7 @@ public function testAbstraction() 'isOptional' => false, 'isPromoted' => false, 'name' => '$param1', - 'type' => 'SomeClass', + 'type' => 'stdClass', ), array( 'attributes' => array(), @@ -874,10 +968,14 @@ public function testAbstraction() 'desc' => null, 'type' => 'void', ), + // 'staticVars' => array() 'visibility' => 'public', ), - $abs['methods']['methodPublic'] + \array_diff_key($abs['methods']['methodPublic'], \array_flip(array('staticVars'))) ); + self::assertSame(42, $abs['methods']['methodPublic']['staticVars']['foo']); + self::assertSame('test', $abs['methods']['methodPublic']['staticVars']['bar']); + self::assertTrue($abs['methods']['methodPublic']['staticVars']['baz']['isRecursion']); } /** @@ -892,7 +990,7 @@ public function testAnonymousClass() $fixtureDir = TEST_DIR . '/Debug/Fixture'; $filepath = $fixtureDir . '/Anonymous.php'; $anonymous = require $filepath; - $line = 26; + $line = 40; $this->testMethod( 'log', @@ -915,14 +1013,23 @@ public function testAnonymousClass() '', ), 'firephp' => 'X-Wf-1-1-1-%d: %d|[{"Label":"anonymous","Type":"LOG"},{"___class_name":"stdClass@anonymous","(public) thing":"hammer","(debug) file":"' . $filepath . '","(debug) line":' . $line . '}]|', - 'html' => '
  • anonymous =
    stdClass@anonymous + 'html' => '
  • anonymous =
    stdClass@anonymous
    -
    extends
    -
    stdClass
    - ' . (PHP_VERSION_ID >= 80200 + ' . (PHP_VERSION_ID >= 80000 ? '
    attributes
    -
    AllowDynamicProperties
    ' +
    AnonymousAttribute
    ' : '') . ' +
    extends
    +
    stdClass
    +
    implements
    +
      +
    • IteratorAggregate +
        +
      • Traversable
      • +
      +
    • +
    +
    constants
    public TWELVE = 12
    properties
    @@ -930,6 +1037,7 @@ public function testAnonymousClass()
    debug string file = ' . $filepath . '
    debug int line = ' . $line . '
    methods
    +
    public getIterator(): Traversable
    public myMethod(): void
  • ', @@ -940,7 +1048,7 @@ public function testAnonymousClass() (debug) file = "%s/PHPDebugConsole/tests/Debug/Fixture/Anonymous.php" (debug) line = %d Methods: - public: 1', + public: 2', ) ); @@ -951,24 +1059,27 @@ public function testAnonymousClass() $anonymous['anonymous'], ), array( - 'entry' => static function (LogEntry $logEntry) use ($filepath) { + 'entry' => static function (LogEntry $logEntry) use ($anonymous, $filepath) { + $reflector = new ReflectionObject($anonymous['anonymous']); $abs = $logEntry['args'][1]; - self::assertArraySubset(array( - 'className' => "\x00default\x00", + // 'className' => "\x00default\x00", + 'className' => 'class@anonymous|' . md5($reflector->getName()), 'definition' => array( 'extensionName' => false, - 'fileName' => '', - 'startLine' => 1, + 'fileName' => $filepath, + 'startLine' => 12, ), - ), $abs->getDefinitionValues()); + ), $abs->getInheritedValues()); self::assertArraySubset(array( 'className' => 'class@anonymous', + /* 'definition' => array( 'extensionName' => false, 'fileName' => $filepath, - 'startLine' => 6, + 'startLine' => 9, ), + */ ), $abs->getValues()); self::assertSame(array( @@ -996,37 +1107,48 @@ public function testAnonymousClass() $anonymous['test1'], ), array( - 'entry' => static function (LogEntry $logEntry) use ($filepath, $fixtureDir) { + 'entry' => static function (LogEntry $logEntry) use ($anonymous, $filepath) { + $reflector = new ReflectionObject($anonymous['test1']); $abs = $logEntry['args'][1]; self::assertArraySubset(array( - 'className' => 'bdk\\Test\\Debug\\Fixture\\AnonBase', + 'className' => 'bdk\Test\Debug\Fixture\AnonBase@anonymous|' . \md5($reflector->getName()), 'definition' => array( 'extensionName' => false, - 'fileName' => $fixtureDir . '/AnonBase.php', - 'startLine' => 8, + 'fileName' => $filepath, + 'startLine' => 69, ), - ), $abs->getDefinitionValues()); + ), $abs->getInheritedValues()); + self::assertArraySubset(array( 'className' => 'bdk\\Test\\Debug\\Fixture\\AnonBase@anonymous', + /* 'definition' => array( 'extensionName' => false, 'fileName' => $filepath, - 'startLine' => 45, + 'startLine' => 54, ), + */ ), $abs->getValues()); self::assertSame(array( + 'PI', 'ONE', - // 'PRIVATE_CONST', - ), \array_keys($abs->getDefinitionValues()['constants'])); + ), \array_keys($abs->getInheritedValues()['constants'])); + self::assertSame(array( + 'color', 'foo', 'pro', - ), \array_keys($abs->getDefinitionValues()['properties'])); + 'debug.file', + 'debug.line', + ), \array_keys($abs->getInheritedValues()['properties'])); + self::assertSame(array( 'test', - ), \array_keys($abs->getDefinitionValues()['methods'])); + 'test1', + 'magic', + ), \array_keys($abs->getInheritedValues()['methods'])); self::assertArraySubset( array( @@ -1044,9 +1166,7 @@ public function testAnonymousClass() $propNames = \array_keys($abs->getInstanceValues()['properties']); \sort($propNames); self::assertSame(array( - 'color', - 'debug.file', - 'debug.line', + 'foo', 'pro', ), $propNames); self::assertArraySubset( @@ -1099,45 +1219,55 @@ public function testAnonymousClass() $anonymous['test2'], ), array( - 'entry' => static function (LogEntry $logEntry) use ($filepath, $fixtureDir) { + 'entry' => static function (LogEntry $logEntry) use ($anonymous, $filepath) { + $reflector = new ReflectionObject($anonymous['test2']); $abs = $logEntry['args'][1]; self::assertArraySubset(array( - 'className' => 'bdk\\Test\\Debug\\Fixture\\AnonBase', + 'className' => 'bdk\Test\Debug\Fixture\AnonBase@anonymous|' . \md5($reflector->getName()), 'definition' => array( 'extensionName' => false, - 'fileName' => $fixtureDir . '/AnonBase.php', - 'startLine' => 8, + 'fileName' => $filepath, + 'startLine' => 80, ), - ), $abs->getDefinitionValues()); + ), $abs->getInheritedValues()); self::assertArraySubset(array( 'className' => 'bdk\\Test\\Debug\\Fixture\\AnonBase@anonymous', + /* 'definition' => array( 'extensionName' => false, 'fileName' => $filepath, - 'startLine' => 56, + 'startLine' => 65, ), + */ ), $abs->getValues()); self::assertSame(array( + 'PI', 'ONE', // 'PRIVATE_CONST', - ), \array_keys($abs->getDefinitionValues()['constants'])); + ), \array_keys($abs->getInheritedValues()['constants'])); self::assertSame(array( + 'color', 'foo', 'pro', - ), \array_keys($abs->getDefinitionValues()['properties'])); + 'debug.file', + 'debug.line', + ), \array_keys($abs->getInheritedValues()['properties'])); self::assertSame(array( 'test', - ), \array_keys($abs->getDefinitionValues()['methods'])); + 'test2', + ), \array_keys($abs->getInheritedValues()['methods'])); }, ) ); // anonymous callable tested via ArrayTest self::assertSame(array( - 'stdClass', "\x00default\x00", - 'bdk\Test\Debug\Fixture\AnonBase', + 'stdClass@anonymous|' . \md5((new ReflectionObject($anonymous['stdClass']))->getName()), + 'class@anonymous|' . \md5((new ReflectionObject($anonymous['anonymous']))->getName()), + 'bdk\Test\Debug\Fixture\AnonBase@anonymous|' . \md5((new ReflectionObject($anonymous['test1']))->getName()), + 'bdk\Test\Debug\Fixture\AnonBase@anonymous|' . \md5((new ReflectionObject($anonymous['test2']))->getName()), ), \array_keys($this->debug->data->get('classDefinitions'))); } @@ -1157,7 +1287,7 @@ public function testDateTime() self::assertSame($dateTime->format(\DateTime::ISO8601), $abs['stringified']); }, 'html' => static function ($htmlActual) use ($dateTime) { - self::assertStringContainsString('
  • dateTime =
    ' . $dateTime->format(\DateTime::ISO8601) . '', $htmlActual); + self::assertStringContainsString('
  • dateTime =
    ' . $dateTime->format(\DateTime::ISO8601) . '', $htmlActual); }, ) ); @@ -1246,7 +1376,7 @@ public function testPhp81() // self::assertSame('Attributed & promoted param', $abs['properties']['arg1']['desc']); }, 'html' => static function ($html) { - $constExpect = '
    public final FINAL_CONST = foo
    '; + $constExpect = '
    final public FINAL_CONST = foo
    '; self::assertStringContainsString($constExpect, $html); $propExpect = '
    public readonly string title = 42
    '; diff --git a/tests/Debug/Utility/SerializeLogTest.php b/tests/Debug/Utility/SerializeLogTest.php index 552288d8..dd345c02 100644 --- a/tests/Debug/Utility/SerializeLogTest.php +++ b/tests/Debug/Utility/SerializeLogTest.php @@ -53,11 +53,7 @@ public function testSerializeUnserialize() $channelNameRoot = $debug->getCfg('channelName', Debug::CONFIG_DEBUG); $expect = array( 'alerts' => $this->helper->deObjectifyData($debug->data->get('alerts'), false), - 'classDefinitions' => array( - 'bdk\\Test\\Debug\\Fixture\\TestObj' => $debug->abstracter->crate(new TestObj())->getDefinitionValues(), - "\x00default\x00" => \bdk\Debug\Utility\Reflection::propGet($debug->abstracter->abstractObject->definition, 'values'), - 'stdClass' => $debug->abstracter->crate((object) array())->getDefinitionValues(), - ), + 'classDefinitions' => $this->helper->deObjectifyData($debug->data->get('classDefinitions')), 'config' => array( 'channelIcon' => $debug->getCfg('channelIcon', Debug::CONFIG_DEBUG), 'channelName' => $channelNameRoot, @@ -78,6 +74,11 @@ public function testSerializeUnserialize() 'requestId' => $debug->data->get('requestId'), 'version' => Debug::VERSION, ); + self::assertSame(array( + "\x00default\x00", + 'bdk\\Test\\Debug\\Fixture\\TestObj', + 'stdClass', + ), \array_keys($unserialized['classDefinitions'])); self::assertEquals( $expect, $this->helper->deObjectifyData($unserialized) @@ -87,7 +88,7 @@ public function testSerializeUnserialize() self::assertInstanceOf('bdk\\Debug\\Abstraction\\Abstraction', $objAbs); self::assertSame( $debug->data->get(array('classDefinitions', 'bdk\\Test\\Debug\\Fixture\\TestObj')), - \bdk\Debug\Utility\Reflection::propGet($objAbs, 'definition') + \bdk\Debug\Utility\Reflection::propGet($objAbs, 'inherited') ); $serialized = SerializeLog::serialize($debug); $unserialized = SerializeLog::unserialize($serialized); @@ -366,12 +367,24 @@ public function testUnserializeLogLegacy() array( 'attributes' => array(), 'cases' => array(), - 'cfgFlags' => 4194303, + 'cfgFlags' => 29360127, + 'interfacesCollapse' => array(), 'isAnonymous' => false, 'isFinal' => false, 'isMaxDepth' => false, 'isReadOnly' => false, + 'sectionOrder' => array( + 'attributes', + 'extends', + 'implements', + 'constants', + 'cases', + 'properties', + 'methods', + 'phpDoc', + ), 'sort' => '', + 'methodsWithStaticVars' => array(), ), $expect['log'][1][1][1] ); @@ -403,12 +416,15 @@ public function testUnserializeLogLegacy() unset($expect['logSummary'][0][$i][2]); } } - self::assertEquals( - \array_intersect_key($expect, \array_flip($keysCompare)), - \array_intersect_key( - $this->helper->deObjectifyData($unserialized, true, false, true), - \array_flip($keysCompare) - ) + + $expect = \array_intersect_key($expect, \array_flip($keysCompare)); + $actual = \array_intersect_key( + $this->helper->deObjectifyData($unserialized, true, false, true), + \array_flip($keysCompare) ); + + // \bdk\Debug::varDump('expect', print_r($expect, true)); + // \bdk\Debug::varDump('actual', print_r($actual, true)); + self::assertEquals($expect, $actual); } }