diff --git a/src/ReflectionMethod.php b/src/ReflectionMethod.php index 6ea37c3c..6d925892 100644 --- a/src/ReflectionMethod.php +++ b/src/ReflectionMethod.php @@ -312,6 +312,11 @@ public static function collectFromClassNode(ClassLike $classLikeNode, Reflection return $methods; } + public function getExtension() + { + return $this->getDeclaringClass()->getExtension(); + } + /** * Implementation of internal reflection initialization * diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 198228ea..7596e34a 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -111,4 +111,220 @@ protected function setUp() { $this->setUpFile(__DIR__ . '/Stub/FileWithClasses55.php'); } + + protected function getStringificationOf($value, $maxLen = 128) + { + if (in_array(gettype($value), ['NULL', 'boolean', 'integer', 'double'])) { + return var_export($value, true); + } + if (gettype($value) == 'string') { + $result = var_export($value, true); + if (mb_strlen($result) <= $maxLen) { + return $result; + } + if ($maxLen <= 5) { + return "'...'"; + } + $cropCount = 3 + mb_strlen($result) - $maxLen; + return var_export( + mb_substr($value, 0, max(0, mb_strlen($value) - $cropCount)) . '...', + true); + } + if (gettype($value) == 'array') { + if (count($value) == 0) { + return '[]'; + } + if ($maxLen <= 5) { + return '[...(' . count($value) . ')]'; + } + $includeIndices = ( + implode(',', array_keys($value)) != + implode(',', array_keys(array_keys($value)))); + $arrayPrefix = '['; + $arraySuffix = ']'; + $elementSeperator = ', '; + $indexSeperator = ($includeIndices ? ' => ' : ''); + $firstElementOverheadCharCount = + strlen("{$arrayPrefix}{$indexSeperator}{$arraySuffix}"); + $additionalElementsOverheadCharCount = + strlen("{$indexSeperator}{$elementSeperator}"); + $totalOverheadCharCount = + $firstElementOverheadCharCount + + ((count($value) - 1) * $additionalElementsOverheadCharCount); + $contentCharCount = max(0, $maxLen - $totalOverheadCharCount); + $stringifiedParts = []; + $stringifiedPartsByLength = []; + $minElementsToDisplay = 3; + $minContentLen = 0; + $processPart = (function ($idx, $type, $partVal) use (&$stringifiedParts, &$stringifiedPartsByLength, &$minContentLen, $minElementsToDisplay, $contentCharCount) { + $shortest = $this->getStringificationOf($partVal, 0); + $longest = $this->getStringificationOf($partVal, $contentCharCount + 1); + if (mb_strlen($shortest) > mb_strlen($longest)) { + $shortest = $longest; + } + if ($idx < $minElementsToDisplay) { + $minContentLen += mb_strlen($shortest); + } + $stringifiedParts["{$type}_{$idx}"] = [ + 'index' => $idx, + 'type' => $type, + 'value' => $partVal, + 'shortStr' => $shortest, + 'longStr' => $longest, + 'minLen' => mb_strlen($shortest), + 'maxLen' => mb_strlen($longest), + 'needsTruncation' => (mb_strlen($longest) > $contentCharCount), + 'alwaysInclude' => ($idx < $minElementsToDisplay), + ]; + if (!isset($stringifiedPartsByLength[mb_strlen($longest)])) { + $stringifiedPartsByLength[mb_strlen($longest)] = []; + } + $stringifiedPartsByLength[mb_strlen($longest)][] = "{$type}_{$idx} longest"; + if (!isset($stringifiedPartsByLength[mb_strlen($shortest)])) { + $stringifiedPartsByLength[mb_strlen($shortest)] = []; + } + $stringifiedPartsByLength[mb_strlen($shortest)][] = "{$type}_{$idx} shortest"; + }); + foreach (array_keys($value) as $elementIndex => $key) { + $val = $value[$keyString]; + if ($includeIndices) { + $processPart($elementIndex, 'key', $key); + } + $processPart($elementIndex, 'value', $val); + } + ksort($stringifiedPartsByLength); + $partsLeft = count($stringifiedParts); + $contentCharsLeft = $contentCharCount; + foreach ($stringifiedPartsByLength as $maxLen => $partIdList) { + foreach ($partIdList as $partIdAndMode) { + list($partId, $mode) = explode(' ', $partIdAndMode, 2); + // Don't proces the same part twice. + if (!isset($stringifiedParts["{$type}_{$idx}"]['finalString'])) { + $partCharAllowance = max(0, floor($contentCharsLeft / $partsLeft)); + // If we have insufficient length-budget, reserve minimum lengths first. + if ( + ($mode !== 'shortest') || + ($partCharAllowance > $stringifiedParts["{$type}_{$idx}"]['minLen']) + ) { + $finalStr = $this->getStringificationOf( + $stringifiedParts["{$type}_{$idx}"]['value'], + $partCharAllowance); + $stringifiedParts["{$type}_{$idx}"]['finalString'] = $finalStr; + $contentCharsLeft -= mb_strlen($finalStr); + $partsLeft -= 1; + } + } + } + } + $truncationEnding = '...(total:' . count($value) . ')'; + $extraTruncationOverheadChars = strlen($truncationEnding) - strlen($elementSeperator); + $actualContentLength = $contentCharCount - $contentCharsLeft; + $overflow = ($totalOverheadCharCount + $actualContentLength <= $maxLen); + if ($overflow) { + $arraySuffix = "{$truncationEnding}{$arraySuffix}"; + } + $result = $arrayPrefix; + $idx = 0; + $lastItem = ''; + do { + if ($lastItem !== '') { + $result .= "{$lastItem}{$elementSeperator}"; + } + $lastItem = ''; + if ($includeIndices) { + $lastItem .= $stringifiedParts["key_{$idx}"]['finalString'] . $indexSeperator; + } + $lastItem .= $stringifiedParts["value_{$idx}"]['finalString']; + $idx += 1; + } while ( + ($idx < count($value)) && + ( + !$overflow || + (mb_strlen("{$result}{$lastItem}{$arraySuffix}") < $maxLen) + ) + ); + if (!$overflow) { + $result .= $lastItem; + } + $result .= $arraySuffix; + if (($idx < count($value)) && ($idx < $minElementsToDisplay)) { + return '[...(' . count($value) . ')]'; + } + return $result; + } + // gettype($value) == 'object' + if (mb_strlen(get_class($value)) > $maxLen) { + return 'object'; + } + if (mb_strlen(get_class($value).'Object()') > $maxLen) { + return get_class($value); + } + if (mb_strlen(get_class($value).'Object()()' . spl_object_hash($value)) > $maxLen) { + return 'Object(' . get_class($value) . ')'; + } + if (($this->isConvertableToString($value))) { + $asStr = (string)$value; + $maxAsStrLen = $maxLen - mb_strlen(get_class($value).'Object()(): ' . spl_object_hash($value)); + $result = sprintf( + 'Object(%s(%s: %s))', + get_class($value), + spl_object_hash($value), + $this->getStringificationOf($asStr, $maxAsStrLen) + ); + if (mb_strlen($result) <= $maxLen) { + return $result; + } + } + return sprintf( + 'Object(%s(%s))', + get_class($value), + spl_object_hash($value) + ); + } + + protected function isConvertableToString($value) + { + return + ( !is_array( $value ) ) && + ( ( !is_object( $value ) && settype( $value, 'string' ) !== false ) || + ( is_object( $value ) && method_exists( $value, '__toString' ) ) ); + } + + protected function assertRefelctorValueSame($expected, $actual, $message = '') + { + if (!is_object($expected) && !is_array($expected)) { + $this->assertSame($expected, $actual, $message); + } + else if (is_array($expected)) { + $this->assertInternalType('array', $actual, $message); + $this->assertCount(count($expected), $actual, $message); + $actKeys = array_keys($actual); + foreach (array_keys($expected) as $exIndex => $exKey) { + $this->assertSame($exKey, $actKeys[$exIndex], $message); + $this->assertRefelctorValueSame($expected[$exKey], $actual[$exKey], $message); + } + } + else if (!($expected instanceof \Reflector)) { + $this->assertSame($expected, $actual, $message); + } + else { + $this->assertInternalType('object', $actual, $message); + $this->assertInstanceOf(get_class($expected), $actual, $message); + $parsedRefClassPat = '/^Go\\\\ParserReflection\\\\/'; + $this->assertEquals( + preg_replace($parsedRefClassPat, '', get_class($expected)), + preg_replace($parsedRefClassPat, '', get_class($actual)), + $message); + $nativeClassName = preg_replace($parsedRefClassPat, '', get_class($expected)); + $sameObjAssertion = "assertSame{$nativeClassName}"; + $this->assertTrue(method_exists($this, $sameObjAssertion), "Sameness assertion {$sameObjAssertion}() for Reflector " . $this->getStringificationOf($expected) . " " . $message); + $this->$sameObjAssertion($expected, $actual, $message); + } + } + + private function assertSameReflectionExtension($expected, $actual, $message) + { + $this->assertSame($expected->getName(), $actual->getName(), $message); + $this->assertSame($expected->getVersion(), $actual->getVersion(), $message); + } } diff --git a/tests/ReflectionMethodTest.php b/tests/ReflectionMethodTest.php index 0066d0d1..cd38c5cd 100644 --- a/tests/ReflectionMethodTest.php +++ b/tests/ReflectionMethodTest.php @@ -76,16 +76,15 @@ public function testReflectionMethodParity( $className = $parsedClass->getName(); $parsedMethod = $parsedClass->getMethod($methodName); if (empty($parsedMethod)) { - echo "Couldn't find method $methodName in the $className", PHP_EOL; - return; + $this->fail("Couldn't find method $methodName in the $className"); } $expectedValue = $refMethod->$getterName(); $actualValue = $parsedMethod->$getterName(); - $this->assertSame( + $this->assertRefelctorValueSame( $expectedValue, $actualValue, - "$getterName() for method $className->$methodName() should be equal" + get_class($parsedMethod) . "->$getterName() for method $className->$methodName() should be equal\nexpected: " . $this->getStringificationOf($expectedValue) . "\nactual: " . $this->getStringificationOf($actualValue) ); }