diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 55089e0be1..bcbc9d9ab8 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -139,10 +139,13 @@ class DeprecationErrorHandler $group = 'unsilenced'; } elseif ($deprecation->isLegacy(self::$utilPrefix)) { $group = 'legacy'; - } elseif (!$deprecation->isSelf()) { - $group = $deprecation->isIndirect() ? 'remaining indirect' : 'remaining direct'; } else { - $group = 'remaining self'; + $group = [ + Deprecation::TYPE_SELF => 'remaining self', + Deprecation::TYPE_DIRECT => 'remaining direct', + Deprecation::TYPE_INDIRECT => 'remaining indirect', + Deprecation::TYPE_UNDETERMINED => 'other', + ][$deprecation->getType()]; } if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) { diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index ba9e753c7b..b70d7c262e 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -18,6 +18,15 @@ use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor; */ class Deprecation { + private const PATH_TYPE_VENDOR = 'path_type_vendor'; + private const PATH_TYPE_SELF = 'path_type_internal'; + private const PATH_TYPE_UNDETERMINED = 'path_type_undetermined'; + + public const TYPE_SELF = 'type_self'; + public const TYPE_DIRECT = 'type_direct'; + public const TYPE_INDIRECT = 'type_indirect'; + public const TYPE_UNDETERMINED = 'type_undetermined'; + /** * @var array */ @@ -39,13 +48,21 @@ class Deprecation private $originMethod; /** - * @var bool + * @var string one of the PATH_TYPE_* constants */ - private $self; + private $triggeringFilePathType; /** @var string[] absolute paths to vendor directories */ private static $vendors; + /** + * @var string[] absolute paths to source or tests of the project. This + * excludes cache directories, because it is based on + * autoloading rules and cache systems typically do not use + * those. + */ + private static $internalPaths; + /** * @param string $message * @param string $file @@ -59,7 +76,7 @@ class Deprecation // No-op } $line = $trace[$i]; - $this->self = !$this->pathOriginatesFromVendor($file); + $this->trigerringFilePathType = $this->getPathType($file); if (isset($line['object']) || isset($line['class'])) { if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) { $parsedMsg = unserialize($this->message); @@ -70,8 +87,9 @@ class Deprecation // \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. - $this->self = isset($parsedMsg['triggering_file']) - && $this->pathOriginatesFromVendor($parsedMsg['triggering_file']); + if (isset($parsedMsg['triggering_file'])) { + $this->trigerringFilePathType = $this->getPathType($parsedMsg['triggering_file']); + } return; } @@ -101,14 +119,6 @@ class Deprecation return isset($this->originClass); } - /** - * @return bool - */ - public function isSelf() - { - return $this->self; - } - /** * @return string */ @@ -163,10 +173,16 @@ class Deprecation * Tells whether both the calling package and the called package are vendor * packages. * - * @return bool + * @return string */ - public function isIndirect() + public function getType() { + if (self::PATH_TYPE_SELF === $this->trigerringFilePathType) { + return self::TYPE_SELF; + } + if (self::PATH_TYPE_UNDETERMINED === $this->trigerringFilePathType) { + return self::TYPE_UNDETERMINED; + } $erroringFile = $erroringPackage = null; foreach ($this->trace as $line) { if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { @@ -179,13 +195,16 @@ class Deprecation if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { continue; } - if (!$this->pathOriginatesFromVendor($file)) { - return false; + if (self::PATH_TYPE_SELF === $this->getPathType($file)) { + return self::TYPE_DIRECT; + } + if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) { + return self::TYPE_UNDETERMINED; } if (null !== $erroringFile && null !== $erroringPackage) { $package = $this->getPackage($file); if ('composer' !== $package && $package !== $erroringPackage) { - return true; + return self::TYPE_INDIRECT; } continue; } @@ -193,11 +212,11 @@ class Deprecation $erroringPackage = $this->getPackage($file); } - return false; + return self::TYPE_DIRECT; } /** - * pathOriginatesFromVendor() should always be called prior to calling this method. + * getPathType() should always be called prior to calling this method. * * @param string $path * @@ -237,6 +256,15 @@ class Deprecation $v = \dirname(\dirname($r->getFileName())); if (file_exists($v.'/composer/installed.json')) { self::$vendors[] = $v; + $loader = require $v.'/autoload.php'; + $paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4())); + } + } + } + foreach ($paths as $path) { + foreach (self::$vendors as $vendor) { + if (0 !== strpos($path, $vendor)) { + self::$internalPaths[] = $path; } } } @@ -245,24 +273,41 @@ class Deprecation return self::$vendors; } + private static function getSourcePathsFromPrefixes(array $prefixesByNamespace) + { + foreach ($prefixesByNamespace as $prefixes) { + foreach ($prefixes as $prefix) { + if (false !== realpath($prefix)) { + yield realpath($prefix); + } + } + } + } + /** * @param string $path * - * @return bool + * @return string */ - private function pathOriginatesFromVendor($path) + private function getPathType($path) { $realPath = realpath($path); if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { - return true; + return self::PATH_TYPE_UNDETERMINED; } foreach (self::getVendors() as $vendor) { if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { - return true; + return self::PATH_TYPE_VENDOR; } } - return false; + foreach (self::$internalPaths as $internalPath) { + if (0 === strpos($realPath, $internalPath)) { + return self::PATH_TYPE_SELF; + } + } + + return self::PATH_TYPE_UNDETERMINED; } /** @@ -281,19 +326,4 @@ class Deprecation "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()). "\n"; } - - private function getPackageFromLine(array $line) - { - if (!isset($line['file'])) { - return 'internal function'; - } - if (!$this->pathOriginatesFromVendor($line['file'])) { - return 'source code'; - } - try { - return $this->getPackage($line['file']); - } catch (\RuntimeException $e) { - return 'unknown'; - } - } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 92bad71e08..583cca22f4 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -27,7 +27,7 @@ class DeprecationTest extends TestCase public function testItCanTellWhetherItIsInternal() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); - $this->assertTrue($deprecation->isSelf()); + $this->assertSame(Deprecation::TYPE_SELF, $deprecation->getType()); } public function testLegacyTestMethodIsDetectedAsSuch() @@ -46,7 +46,7 @@ class DeprecationTest extends TestCase public function testItRulesOutFilesOutsideVendorsAsIndirect() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); - $this->assertFalse($deprecation->isIndirect()); + $this->assertNotSame(Deprecation::TYPE_INDIRECT, $deprecation->getType()); } /** diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index e9f7bec966..126d23389a 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -73,15 +73,13 @@ Unsilenced deprecation notices (3) 1x: unsilenced bar deprecation 1x in FooTestCase::testNonLegacyBar -Remaining self deprecation notices (1) +Legacy deprecation notices (1) + +Other deprecation notices (2) + + 1x: root deprecation 1x: silenced bar deprecation 1x in FooTestCase::testNonLegacyBar -Legacy deprecation notices (1) - -Other deprecation notices (1) - - 1x: root deprecation - I get precedence over any exit statements inside the deprecation error handler. diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php index bf315f2eaa..3c4471bcbe 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/autoload.php @@ -1,3 +1,5 @@