From 9df76ebbe5f386df3e1c328183cc2fdc29659166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 30 Dec 2018 10:03:48 +0100 Subject: [PATCH 1/2] Extract closures into static functions --- .../PhpUnit/DeprecationErrorHandler.php | 436 ++++++++++-------- 1 file changed, 239 insertions(+), 197 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index d18e161dd9..2fa32bdaa9 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -22,7 +22,22 @@ class DeprecationErrorHandler const MODE_WEAK_VENDORS = 'weak_vendors'; const MODE_DISABLED = 'disabled'; + private static $rawMode = false; + private static $mode = false; private static $isRegistered = false; + private static $deprecations = [ + 'unsilencedCount' => 0, + 'remainingCount' => 0, + 'legacyCount' => 0, + 'otherCount' => 0, + 'remaining vendorCount' => 0, + 'unsilenced' => [], + 'remaining' => [], + 'legacy' => [], + 'other' => [], + 'remaining vendor' => [], + ]; + private static $utilPrefix = ''; /** * Registers and configures the deprecation handler. @@ -35,188 +50,38 @@ class DeprecationErrorHandler * - use a number to define the upper bound of allowed deprecations, * making the test suite fail whenever more notices are triggered. * - * @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations + * @param int|string|false $rawMode The reporting mode, defaults to not allowing any deprecations */ - public static function register($mode = 0) + public static function register($rawMode = 0) { if (self::$isRegistered) { return; } - $UtilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\'; + static::$rawMode = $rawMode; - $getMode = function () use ($mode) { - static $memoizedMode = false; - - if (false !== $memoizedMode) { - return $memoizedMode; - } - if (false === $mode) { - $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); - } - if (self::MODE_DISABLED !== $mode - && self::MODE_WEAK !== $mode - && self::MODE_WEAK_VENDORS !== $mode - && (!isset($mode[0]) || '/' !== $mode[0]) - ) { - $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0; - } - - return $memoizedMode = $mode; - }; - - $inVendors = function ($path) { - /** @var string[] absolute paths to vendor directories */ - static $vendors; - if (null === $vendors) { - foreach (get_declared_classes() as $class) { - if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { - $r = new \ReflectionClass($class); - $v = \dirname(\dirname($r->getFileName())); - if (file_exists($v.'/composer/installed.json')) { - $vendors[] = $v; - } - } - } - } - $realPath = realpath($path); - if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { - return true; - } - foreach ($vendors as $vendor) { - if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { - return true; - } - } - - return false; - }; - - $deprecations = [ - 'unsilencedCount' => 0, - 'remainingCount' => 0, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining vendorCount' => 0, - 'unsilenced' => [], - 'remaining' => [], - 'legacy' => [], - 'other' => [], - 'remaining vendor' => [], - ]; - $deprecationHandler = function ($type, $msg, $file, $line, $context = []) use (&$deprecations, $getMode, $UtilPrefix, $inVendors) { - $mode = $getMode(); - if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || self::MODE_DISABLED === $mode) { - $ErrorHandler = $UtilPrefix.'ErrorHandler'; - - return $ErrorHandler::handleError($type, $msg, $file, $line, $context); - } - - $trace = debug_backtrace(); - $group = 'other'; - $isVendor = self::MODE_WEAK_VENDORS === $mode && $inVendors($file); - - $i = \count($trace); - while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { - // No-op - } - - if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { - if (isset($trace[$i]['class']) && 0 === strpos($trace[$i]['class'], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor')) { - $parsedMsg = unserialize($msg); - $msg = $parsedMsg['deprecation']; - $class = $parsedMsg['class']; - $method = $parsedMsg['method']; - // If the deprecation has been triggered via - // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() - // then we need to use the serialized information to determine - // if the error has been triggered from vendor code. - $isVendor = self::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && $inVendors($parsedMsg['triggering_file']); - } else { - $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; - $method = $trace[$i]['function']; - } - - $Test = $UtilPrefix.'Test'; - - if (0 !== error_reporting()) { - $group = 'unsilenced'; - } elseif (0 === strpos($method, 'testLegacy') - || 0 === strpos($method, 'provideLegacy') - || 0 === strpos($method, 'getLegacy') - || strpos($class, '\Legacy') - || \in_array('legacy', $Test::getGroups($class, $method), true) - ) { - $group = 'legacy'; - } elseif ($isVendor) { - $group = 'remaining vendor'; - } else { - $group = 'remaining'; - } - - if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { - $e = new \Exception($msg); - $r = new \ReflectionProperty($e, 'trace'); - $r->setAccessible(true); - $r->setValue($e, \array_slice($trace, 1, $i)); - - echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; - echo "\n".$msg; - echo "\nStack trace:"; - echo "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); - echo "\n"; - - exit(1); - } - if ('legacy' !== $group && self::MODE_WEAK !== $mode) { - $ref = &$deprecations[$group][$msg]['count']; - ++$ref; - $ref = &$deprecations[$group][$msg][$class.'::'.$method]; - ++$ref; - } - } elseif (self::MODE_WEAK !== $mode) { - $ref = &$deprecations[$group][$msg]['count']; - ++$ref; - } - ++$deprecations[$group.'Count']; - }; - $oldErrorHandler = set_error_handler($deprecationHandler); + $oldErrorHandler = set_error_handler([self::class, 'handleError']); if (null !== $oldErrorHandler) { restore_error_handler(); - if ([$UtilPrefix.'ErrorHandler', 'handleError'] === $oldErrorHandler) { + if ([self::utilPrefix().'ErrorHandler', 'handleError'] === $oldErrorHandler) { restore_error_handler(); - self::register($mode); + self::register($rawMode); } } else { self::$isRegistered = true; - if (self::hasColorSupport()) { - $colorize = function ($str, $red) { - $color = $red ? '41;37' : '43;30'; - - return "\x1B[{$color}m{$str}\x1B[0m"; - }; - } else { - $colorize = function ($str) { return $str; }; - } - register_shutdown_function(function () use ($getMode, &$deprecations, $deprecationHandler, $colorize) { - $mode = $getMode(); + register_shutdown_function(function () use ($rawMode) { + self::computeMode($rawMode); + $mode = self::$mode; if (isset($mode[0]) && '/' === $mode[0]) { return; } $currErrorHandler = set_error_handler('var_dump'); restore_error_handler(); - if (self::MODE_WEAK === $mode) { - $colorize = function ($str) { return $str; }; + if ($currErrorHandler !== [self::class, 'handleError']) { + echo "\n", self::colorize('THE ERROR HANDLER HAS CHANGED!', true, $mode), "\n"; } - if ($currErrorHandler !== $deprecationHandler) { - echo "\n", $colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n"; - } - - $cmp = function ($a, $b) { - return $b['count'] - $a['count']; - }; $groups = ['unsilenced', 'remaining']; if (self::MODE_WEAK_VENDORS === $mode) { @@ -224,53 +89,25 @@ class DeprecationErrorHandler } array_push($groups, 'legacy', 'other'); - $displayDeprecations = function ($deprecations) use ($colorize, $cmp, $groups) { - foreach ($groups as $group) { - if ($deprecations[$group.'Count']) { - echo "\n", $colorize( - sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), - 'legacy' !== $group && 'remaining vendor' !== $group - ), "\n"; - - uasort($deprecations[$group], $cmp); - - foreach ($deprecations[$group] as $msg => $notices) { - echo "\n ", $notices['count'], 'x: ', $msg, "\n"; - - arsort($notices); - - foreach ($notices as $method => $count) { - if ('count' !== $method) { - echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; - } - } - } - } - } - if (!empty($notices)) { - echo "\n"; - } - }; - - $displayDeprecations($deprecations); + self::displayDeprecations($groups, $mode); // store failing status - $isFailing = self::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']; + $isFailing = self::MODE_WEAK !== $mode && $mode < self::$deprecations['unsilencedCount'] + self::$deprecations['remainingCount'] + self::$deprecations['otherCount']; // reset deprecations array - foreach ($deprecations as $group => $arrayOrInt) { - $deprecations[$group] = \is_int($arrayOrInt) ? 0 : []; + foreach (self::$deprecations as $group => $arrayOrInt) { + self::$deprecations[$group] = \is_int($arrayOrInt) ? 0 : []; } - register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) { - foreach ($deprecations as $group => $arrayOrInt) { + register_shutdown_function(function () use ($isFailing, $groups, $mode) { + foreach (self::$deprecations as $group => $arrayOrInt) { if (0 < (\is_int($arrayOrInt) ? $arrayOrInt : \count($arrayOrInt))) { echo "Shutdown-time deprecations:\n"; break; } } - $displayDeprecations($deprecations); - if ($isFailing || self::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { + self::displayDeprecations($groups, $mode); + if ($isFailing || self::MODE_WEAK !== $mode && $mode < self::$deprecations['unsilencedCount'] + self::$deprecations['remainingCount'] + self::$deprecations['otherCount']) { exit(1); } }); @@ -339,4 +176,209 @@ class DeprecationErrorHandler // Check if formatted mode is S_IFCHR return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } + + /** + * @param mixed $mode + */ + private static function computeMode($mode) + { + if (false !== self::$mode) { + return; + } + if (false === $mode) { + $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); + } + if (self::MODE_DISABLED !== $mode + && self::MODE_WEAK !== $mode + && self::MODE_WEAK_VENDORS !== $mode + && (!isset($mode[0]) || '/' !== $mode[0]) + ) { + $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0; + } + + self::$mode = $mode; + } + + /** + * @param string $path + * + * @return bool + */ + private static function inVendors($path) + { + /** @var string[] absolute paths to vendor directories */ + static $vendors; + if (null === $vendors) { + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname(\dirname($r->getFileName())); + if (file_exists($v.'/composer/installed.json')) { + $vendors[] = $v; + } + } + } + } + $realPath = realpath($path); + if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { + return true; + } + foreach ($vendors as $vendor) { + if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + return true; + } + } + + return false; + } + + /** + * @internal + */ + public static function handleError($type, $msg, $file, $line, $context = []) + { + self::computeMode(self::$rawMode); + $mode = self::$mode; + if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || self::MODE_DISABLED === $mode) { + $ErrorHandler = self::utilPrefix().'ErrorHandler'; + + return $ErrorHandler::handleError($type, $msg, $file, $line, $context); + } + + $trace = debug_backtrace(); + $group = 'other'; + $isVendor = self::MODE_WEAK_VENDORS === $mode && self::inVendors($file); + + $i = \count($trace); + while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { + // No-op + } + + if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { + if (isset($trace[$i]['class']) && 0 === strpos($trace[$i]['class'], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor')) { + $parsedMsg = unserialize($msg); + $msg = $parsedMsg['deprecation']; + $class = $parsedMsg['class']; + $method = $parsedMsg['method']; + // If the deprecation has been triggered via + // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() + // then we need to use the serialized information to determine + // if the error has been triggered from vendor code. + $isVendor = self::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && self::inVendors($parsedMsg['triggering_file']); + } else { + $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; + $method = $trace[$i]['function']; + } + + $Test = self::utilPrefix().'Test'; + + if (0 !== error_reporting()) { + $group = 'unsilenced'; + } elseif (0 === strpos($method, 'testLegacy') + || 0 === strpos($method, 'provideLegacy') + || 0 === strpos($method, 'getLegacy') + || strpos($class, '\Legacy') + || \in_array('legacy', $Test::getGroups($class, $method), true) + ) { + $group = 'legacy'; + } elseif ($isVendor) { + $group = 'remaining vendor'; + } else { + $group = 'remaining'; + } + + if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { + $e = new \Exception($msg); + $r = new \ReflectionProperty($e, 'trace'); + $r->setAccessible(true); + $r->setValue($e, \array_slice($trace, 1, $i)); + + echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; + echo "\n".$msg; + echo "\nStack trace:"; + echo "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); + echo "\n"; + + exit(1); + } + if ('legacy' !== $group && self::MODE_WEAK !== $mode) { + $ref = &self::$deprecations[$group][$msg]['count']; + ++$ref; + $ref = &self::$deprecations[$group][$msg][$class.'::'.$method]; + ++$ref; + } + } elseif (self::MODE_WEAK !== $mode) { + $ref = &self::$deprecations[$group][$msg]['count']; + ++$ref; + } + ++self::$deprecations[$group.'Count']; + } + + /** + * @param string $str + * @param bool $red + * @param mixed $mode + * + * @return string + */ + private static function colorize($str, $red, $mode) + { + if (!self::hasColorSupport() || self::MODE_WEAK === $mode) { + return $str; + } + + $color = $red ? '41;37' : '43;30'; + + return "\x1B[{$color}m{$str}\x1B[0m"; + } + + /** + * @param string[] $groups + * @param mixed $mode + */ + private static function displayDeprecations($groups, $mode) + { + $cmp = function ($a, $b) { + return $b['count'] - $a['count']; + }; + + foreach ($groups as $group) { + if (self::$deprecations[$group.'Count']) { + echo "\n", self::colorize( + sprintf('%s deprecation notices (%d)', ucfirst($group), self::$deprecations[$group.'Count']), + 'legacy' !== $group && 'remaining vendor' !== $group, + $mode + ), "\n"; + + uasort(self::$deprecations[$group], $cmp); + + foreach (self::$deprecations[$group] as $msg => $notices) { + echo "\n ", $notices['count'], 'x: ', $msg, "\n"; + + arsort($notices); + + foreach ($notices as $method => $count) { + if ('count' !== $method) { + echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + } + } + } + } + } + if (!empty($notices)) { + echo "\n"; + } + } + + /** + * @return string + */ + private static function utilPrefix() + { + if ('' !== self::$utilPrefix) { + return self::$utilPrefix; + } + + return self::$utilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\'; + } } From 97f3023963d1937d2ffa14a982ab42b55de5e518 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 27 Jan 2019 18:58:44 +0100 Subject: [PATCH 2/2] Use the scope of an instance --- .../PhpUnit/DeprecationErrorHandler.php | 533 +++++++++--------- 1 file changed, 275 insertions(+), 258 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 2fa32bdaa9..950664a7c0 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -22,10 +22,9 @@ class DeprecationErrorHandler const MODE_WEAK_VENDORS = 'weak_vendors'; const MODE_DISABLED = 'disabled'; - private static $rawMode = false; - private static $mode = false; - private static $isRegistered = false; - private static $deprecations = [ + private $mode = false; + private $resolvedMode = false; + private $deprecations = [ 'unsilencedCount' => 0, 'remainingCount' => 0, 'legacyCount' => 0, @@ -37,7 +36,9 @@ class DeprecationErrorHandler 'other' => [], 'remaining vendor' => [], ]; - private static $utilPrefix = ''; + + private static $isRegistered = false; + private static $utilPrefix; /** * Registers and configures the deprecation handler. @@ -50,68 +51,30 @@ class DeprecationErrorHandler * - use a number to define the upper bound of allowed deprecations, * making the test suite fail whenever more notices are triggered. * - * @param int|string|false $rawMode The reporting mode, defaults to not allowing any deprecations + * @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations */ - public static function register($rawMode = 0) + public static function register($mode = 0) { if (self::$isRegistered) { return; } - static::$rawMode = $rawMode; + self::$utilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\'; - $oldErrorHandler = set_error_handler([self::class, 'handleError']); + $handler = new self(); + $oldErrorHandler = set_error_handler([$handler, 'handleError']); if (null !== $oldErrorHandler) { restore_error_handler(); - if ([self::utilPrefix().'ErrorHandler', 'handleError'] === $oldErrorHandler) { + + if ([self::$utilPrefix.'ErrorHandler', 'handleError'] === $oldErrorHandler) { restore_error_handler(); - self::register($rawMode); + self::register($mode); } } else { + $handler->mode = $mode; self::$isRegistered = true; - register_shutdown_function(function () use ($rawMode) { - self::computeMode($rawMode); - $mode = self::$mode; - if (isset($mode[0]) && '/' === $mode[0]) { - return; - } - $currErrorHandler = set_error_handler('var_dump'); - restore_error_handler(); - - if ($currErrorHandler !== [self::class, 'handleError']) { - echo "\n", self::colorize('THE ERROR HANDLER HAS CHANGED!', true, $mode), "\n"; - } - - $groups = ['unsilenced', 'remaining']; - if (self::MODE_WEAK_VENDORS === $mode) { - $groups[] = 'remaining vendor'; - } - array_push($groups, 'legacy', 'other'); - - self::displayDeprecations($groups, $mode); - - // store failing status - $isFailing = self::MODE_WEAK !== $mode && $mode < self::$deprecations['unsilencedCount'] + self::$deprecations['remainingCount'] + self::$deprecations['otherCount']; - - // reset deprecations array - foreach (self::$deprecations as $group => $arrayOrInt) { - self::$deprecations[$group] = \is_int($arrayOrInt) ? 0 : []; - } - - register_shutdown_function(function () use ($isFailing, $groups, $mode) { - foreach (self::$deprecations as $group => $arrayOrInt) { - if (0 < (\is_int($arrayOrInt) ? $arrayOrInt : \count($arrayOrInt))) { - echo "Shutdown-time deprecations:\n"; - break; - } - } - self::displayDeprecations($groups, $mode); - if ($isFailing || self::MODE_WEAK !== $mode && $mode < self::$deprecations['unsilencedCount'] + self::$deprecations['remainingCount'] + self::$deprecations['otherCount']) { - exit(1); - } - }); - }); + register_shutdown_function([$handler, 'shutdown']); } } @@ -123,6 +86,7 @@ class DeprecationErrorHandler if ($previousErrorHandler) { return $previousErrorHandler($type, $msg, $file, $line, $context); } + static $autoload = true; $ErrorHandler = class_exists('PHPUnit_Util_ErrorHandler', $autoload) ? 'PHPUnit_Util_ErrorHandler' : 'PHPUnit\Util\ErrorHandler'; @@ -130,6 +94,7 @@ class DeprecationErrorHandler return $ErrorHandler::handleError($type, $msg, $file, $line, $context); } + $deprecations[] = [error_reporting(), $msg, $file]; }); @@ -138,6 +103,262 @@ class DeprecationErrorHandler }); } + /** + * @internal + */ + public function handleError($type, $msg, $file, $line, $context = []) + { + $mode = $this->getMode(); + + if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || self::MODE_DISABLED === $mode) { + $ErrorHandler = self::$utilPrefix.'ErrorHandler'; + + return $ErrorHandler::handleError($type, $msg, $file, $line, $context); + } + + $trace = debug_backtrace(); + $group = 'other'; + $isVendor = self::MODE_WEAK_VENDORS === $mode && self::inVendors($file); + + $i = \count($trace); + while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { + // No-op + } + + if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { + if (isset($trace[$i]['class']) && 0 === strpos($trace[$i]['class'], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor')) { + $parsedMsg = unserialize($msg); + $msg = $parsedMsg['deprecation']; + $class = $parsedMsg['class']; + $method = $parsedMsg['method']; + // If the deprecation has been triggered via + // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() + // then we need to use the serialized information to determine + // if the error has been triggered from vendor code. + $isVendor = self::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && self::inVendors($parsedMsg['triggering_file']); + } else { + $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; + $method = $trace[$i]['function']; + } + + $Test = self::$utilPrefix.'Test'; + + if (0 !== error_reporting()) { + $group = 'unsilenced'; + } elseif (0 === strpos($method, 'testLegacy') + || 0 === strpos($method, 'provideLegacy') + || 0 === strpos($method, 'getLegacy') + || strpos($class, '\Legacy') + || \in_array('legacy', $Test::getGroups($class, $method), true) + ) { + $group = 'legacy'; + } elseif ($isVendor) { + $group = 'remaining vendor'; + } else { + $group = 'remaining'; + } + + if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { + $e = new \Exception($msg); + $r = new \ReflectionProperty($e, 'trace'); + $r->setAccessible(true); + $r->setValue($e, \array_slice($trace, 1, $i)); + + echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; + echo "\n".$msg; + echo "\nStack trace:"; + echo "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); + echo "\n"; + + exit(1); + } + + if ('legacy' !== $group && self::MODE_WEAK !== $mode) { + $ref = &$this->deprecations[$group][$msg]['count']; + ++$ref; + $ref = &$this->deprecations[$group][$msg][$class.'::'.$method]; + ++$ref; + } + } elseif (self::MODE_WEAK !== $mode) { + $ref = &$this->deprecations[$group][$msg]['count']; + ++$ref; + } + + ++$this->deprecations[$group.'Count']; + } + + /** + * @internal + */ + public function shutdown() + { + $mode = $this->getMode(); + + if (isset($mode[0]) && '/' === $mode[0]) { + return; + } + + $currErrorHandler = set_error_handler('var_dump'); + restore_error_handler(); + + if ($currErrorHandler !== [$this, 'handleError']) { + echo "\n", self::colorize('THE ERROR HANDLER HAS CHANGED!', true, $mode), "\n"; + } + + $groups = ['unsilenced', 'remaining']; + + if (self::MODE_WEAK_VENDORS === $mode) { + $groups[] = 'remaining vendor'; + } + + array_push($groups, 'legacy', 'other'); + + $this->displayDeprecations($groups, $mode); + + // store failing status + $isFailing = self::MODE_WEAK !== $mode && $mode < $this->deprecations['unsilencedCount'] + $this->deprecations['remainingCount'] + $this->deprecations['otherCount']; + + // reset deprecations array + foreach ($this->deprecations as $group => $arrayOrInt) { + $this->deprecations[$group] = \is_int($arrayOrInt) ? 0 : []; + } + + register_shutdown_function(function () use ($isFailing, $groups, $mode) { + foreach ($this->deprecations as $group => $arrayOrInt) { + if (0 < (\is_int($arrayOrInt) ? $arrayOrInt : \count($arrayOrInt))) { + echo "Shutdown-time deprecations:\n"; + break; + } + } + + $this->displayDeprecations($groups, $mode); + + if ($isFailing || self::MODE_WEAK !== $mode && $mode < $this->deprecations['unsilencedCount'] + $this->deprecations['remainingCount'] + $this->deprecations['otherCount']) { + exit(1); + } + }); + } + + private function getMode() + { + if (false !== $this->resolvedMode) { + return $this->resolvedMode; + } + + if (false === $mode = $this->mode) { + $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); + } + + if (self::MODE_DISABLED !== $mode + && self::MODE_WEAK !== $mode + && self::MODE_WEAK_VENDORS !== $mode + && (!isset($mode[0]) || '/' !== $mode[0]) + ) { + $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0; + } + + return $this->resolvedMode = $mode; + } + + /** + * @param string $path + * + * @return bool + */ + private static function inVendors($path) + { + /** @var string[] absolute paths to vendor directories */ + static $vendors; + + if (null === $vendors) { + foreach (get_declared_classes() as $class) { + if ('C' !== $class[0] || 0 !== strpos($class, 'ComposerAutoloaderInit')) { + continue; + } + + $r = new \ReflectionClass($class); + $v = \dirname(\dirname($r->getFileName())); + + if (file_exists($v.'/composer/installed.json')) { + $vendors[] = $v; + } + } + } + + $realPath = realpath($path); + + if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { + return true; + } + + foreach ($vendors as $vendor) { + if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + return true; + } + } + + return false; + } + + /** + * @param string $str + * @param bool $red + * @param mixed $mode + * + * @return string + */ + private static function colorize($str, $red, $mode) + { + if (!self::hasColorSupport() || self::MODE_WEAK === $mode) { + return $str; + } + + $color = $red ? '41;37' : '43;30'; + + return "\x1B[{$color}m{$str}\x1B[0m"; + } + + /** + * @param string[] $groups + * @param mixed $mode + */ + private function displayDeprecations($groups, $mode) + { + $cmp = function ($a, $b) { + return $b['count'] - $a['count']; + }; + + foreach ($groups as $group) { + if (!$this->deprecations[$group.'Count']) { + continue; + } + + echo "\n", self::colorize( + sprintf('%s deprecation notices (%d)', ucfirst($group), $this->deprecations[$group.'Count']), + 'legacy' !== $group && 'remaining vendor' !== $group, + $mode + ), "\n"; + + uasort($this->deprecations[$group], $cmp); + + foreach ($this->deprecations[$group] as $msg => $notices) { + echo "\n ", $notices['count'], 'x: ', $msg, "\n"; + + arsort($notices); + + foreach ($notices as $method => $count) { + if ('count' !== $method) { + echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + } + } + } + } + + if (!empty($notices)) { + echo "\n"; + } + } + /** * Returns true if STDOUT is defined and supports colorization. * @@ -173,212 +394,8 @@ class DeprecationErrorHandler } $stat = fstat(STDOUT); + // Check if formatted mode is S_IFCHR return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } - - /** - * @param mixed $mode - */ - private static function computeMode($mode) - { - if (false !== self::$mode) { - return; - } - if (false === $mode) { - $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); - } - if (self::MODE_DISABLED !== $mode - && self::MODE_WEAK !== $mode - && self::MODE_WEAK_VENDORS !== $mode - && (!isset($mode[0]) || '/' !== $mode[0]) - ) { - $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0; - } - - self::$mode = $mode; - } - - /** - * @param string $path - * - * @return bool - */ - private static function inVendors($path) - { - /** @var string[] absolute paths to vendor directories */ - static $vendors; - if (null === $vendors) { - foreach (get_declared_classes() as $class) { - if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { - $r = new \ReflectionClass($class); - $v = \dirname(\dirname($r->getFileName())); - if (file_exists($v.'/composer/installed.json')) { - $vendors[] = $v; - } - } - } - } - $realPath = realpath($path); - if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { - return true; - } - foreach ($vendors as $vendor) { - if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { - return true; - } - } - - return false; - } - - /** - * @internal - */ - public static function handleError($type, $msg, $file, $line, $context = []) - { - self::computeMode(self::$rawMode); - $mode = self::$mode; - if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || self::MODE_DISABLED === $mode) { - $ErrorHandler = self::utilPrefix().'ErrorHandler'; - - return $ErrorHandler::handleError($type, $msg, $file, $line, $context); - } - - $trace = debug_backtrace(); - $group = 'other'; - $isVendor = self::MODE_WEAK_VENDORS === $mode && self::inVendors($file); - - $i = \count($trace); - while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { - // No-op - } - - if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { - if (isset($trace[$i]['class']) && 0 === strpos($trace[$i]['class'], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor')) { - $parsedMsg = unserialize($msg); - $msg = $parsedMsg['deprecation']; - $class = $parsedMsg['class']; - $method = $parsedMsg['method']; - // If the deprecation has been triggered via - // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() - // then we need to use the serialized information to determine - // if the error has been triggered from vendor code. - $isVendor = self::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && self::inVendors($parsedMsg['triggering_file']); - } else { - $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; - $method = $trace[$i]['function']; - } - - $Test = self::utilPrefix().'Test'; - - if (0 !== error_reporting()) { - $group = 'unsilenced'; - } elseif (0 === strpos($method, 'testLegacy') - || 0 === strpos($method, 'provideLegacy') - || 0 === strpos($method, 'getLegacy') - || strpos($class, '\Legacy') - || \in_array('legacy', $Test::getGroups($class, $method), true) - ) { - $group = 'legacy'; - } elseif ($isVendor) { - $group = 'remaining vendor'; - } else { - $group = 'remaining'; - } - - if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { - $e = new \Exception($msg); - $r = new \ReflectionProperty($e, 'trace'); - $r->setAccessible(true); - $r->setValue($e, \array_slice($trace, 1, $i)); - - echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; - echo "\n".$msg; - echo "\nStack trace:"; - echo "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); - echo "\n"; - - exit(1); - } - if ('legacy' !== $group && self::MODE_WEAK !== $mode) { - $ref = &self::$deprecations[$group][$msg]['count']; - ++$ref; - $ref = &self::$deprecations[$group][$msg][$class.'::'.$method]; - ++$ref; - } - } elseif (self::MODE_WEAK !== $mode) { - $ref = &self::$deprecations[$group][$msg]['count']; - ++$ref; - } - ++self::$deprecations[$group.'Count']; - } - - /** - * @param string $str - * @param bool $red - * @param mixed $mode - * - * @return string - */ - private static function colorize($str, $red, $mode) - { - if (!self::hasColorSupport() || self::MODE_WEAK === $mode) { - return $str; - } - - $color = $red ? '41;37' : '43;30'; - - return "\x1B[{$color}m{$str}\x1B[0m"; - } - - /** - * @param string[] $groups - * @param mixed $mode - */ - private static function displayDeprecations($groups, $mode) - { - $cmp = function ($a, $b) { - return $b['count'] - $a['count']; - }; - - foreach ($groups as $group) { - if (self::$deprecations[$group.'Count']) { - echo "\n", self::colorize( - sprintf('%s deprecation notices (%d)', ucfirst($group), self::$deprecations[$group.'Count']), - 'legacy' !== $group && 'remaining vendor' !== $group, - $mode - ), "\n"; - - uasort(self::$deprecations[$group], $cmp); - - foreach (self::$deprecations[$group] as $msg => $notices) { - echo "\n ", $notices['count'], 'x: ', $msg, "\n"; - - arsort($notices); - - foreach ($notices as $method => $count) { - if ('count' !== $method) { - echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; - } - } - } - } - } - if (!empty($notices)) { - echo "\n"; - } - } - - /** - * @return string - */ - private static function utilPrefix() - { - if ('' !== self::$utilPrefix) { - return self::$utilPrefix; - } - - return self::$utilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\'; - } }