[Debug] Trigger a deprecation when using an internal class/trait/interface

This commit is contained in:
Guilhem Niot 2017-08-06 15:38:20 +02:00 committed by Nicolas Grekas
parent 736f0d0d93
commit b89ba293dd
13 changed files with 120 additions and 58 deletions

View File

@ -20,7 +20,7 @@ use Symfony\Component\Form\Exception\RuntimeException;
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* *
* @internal This class is meant for internal use only. * @internal
*/ */
class IdReader class IdReader
{ {

View File

@ -27,6 +27,7 @@ class DebugClassLoader
private $classLoader; private $classLoader;
private $isFinder; private $isFinder;
private static $caseCheck; private static $caseCheck;
private static $internal = array();
private static $final = array(); private static $final = array();
private static $finalMethods = array(); private static $finalMethods = array();
private static $deprecated = array(); private static $deprecated = array();
@ -166,10 +167,14 @@ class DebugClassLoader
} }
$parent = get_parent_class($class); $parent = get_parent_class($class);
$doc = $refl->getDocComment();
if (preg_match('#\n \* @internal(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
self::$internal[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
}
// Not an interface nor a trait // Not an interface nor a trait
if (class_exists($name, false)) { if (class_exists($name, false)) {
if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
} }
@ -203,50 +208,57 @@ class DebugClassLoader
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
@trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { }
if (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
}
// Don't trigger deprecations for classes in the same vendor
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
$len = 0;
$ns = '';
} else { } else {
// Don't trigger deprecations for classes in the same vendor switch ($ns = substr($name, 0, $len)) {
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { case 'Symfony\Bridge\\':
$len = 0; case 'Symfony\Bundle\\':
$ns = ''; case 'Symfony\Component\\':
} else { $ns = 'Symfony\\';
switch ($ns = substr($name, 0, $len)) { $len = strlen($ns);
case 'Symfony\Bridge\\': break;
case 'Symfony\Bundle\\': }
case 'Symfony\Component\\': }
$ns = 'Symfony\\';
$len = strlen($ns); foreach (array_merge(array($parent), class_implements($name, false), class_uses($name, false)) as $use) {
break; if (isset(self::$internal[$use]) && strncmp($ns, $use, $len)) {
@trigger_error(sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $name), E_USER_DEPRECATED);
}
}
if (!$parent || strncmp($ns, $parent, $len)) {
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
@trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
}
$parentInterfaces = array();
$deprecatedInterfaces = array();
if ($parent) {
foreach (class_implements($parent) as $interface) {
$parentInterfaces[$interface] = 1;
} }
} }
if (!$parent || strncmp($ns, $parent, $len)) { foreach ($refl->getInterfaceNames() as $interface) {
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
@trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); $deprecatedInterfaces[] = $interface;
} }
foreach (class_implements($interface) as $interface) {
$parentInterfaces = array(); $parentInterfaces[$interface] = 1;
$deprecatedInterfaces = array();
if ($parent) {
foreach (class_implements($parent) as $interface) {
$parentInterfaces[$interface] = 1;
}
} }
}
foreach ($refl->getInterfaceNames() as $interface) { foreach ($deprecatedInterfaces as $interface) {
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { if (!isset($parentInterfaces[$interface])) {
$deprecatedInterfaces[] = $interface; @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
}
foreach (class_implements($interface) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
foreach ($deprecatedInterfaces as $interface) {
if (!isset($parentInterfaces[$interface])) {
@trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
}
} }
} }
} }

View File

@ -312,6 +312,24 @@ class DebugClassLoaderTest extends TestCase
$this->assertSame($xError, $lastError); $this->assertSame($xError, $lastError);
} }
public function testInternalsUse()
{
$deprecations = array();
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
$e = error_reporting(E_USER_DEPRECATED);
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
error_reporting($e);
restore_error_handler();
$this->assertSame($deprecations, array(
'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal since version 3.4. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
));
}
} }
class ClassLoader class ClassLoader
@ -335,22 +353,12 @@ class ClassLoader
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return $fixtureDir.'CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) { } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return $fixtureDir.'reallyNotPsr0.php'; return $fixtureDir.'reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) { } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
return $fixtureDir.'notPsr0Bis.php'; return $fixtureDir.'notPsr0Bis.php';
} elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) {
return $fixtureDir.'DeprecatedInterface.php';
} elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) {
return $fixtureDir.'FinalClass.php';
} elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) {
return $fixtureDir.'FinalMethod.php';
} elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) {
return $fixtureDir.'ExtendedFinalMethod.php';
} elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
@ -363,6 +371,10 @@ class ClassLoader
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) { } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}'); eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface {
use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
}');
} }
} }
} }

View File

@ -0,0 +1,11 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal since version 3.4.
*/
class InternalClass
{
use InternalTrait2;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
interface InternalInterface
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
trait InternalTrait
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @internal
*/
trait InternalTrait2
{
}

View File

@ -18,7 +18,7 @@ use Symfony\Component\Cache\CacheItem;
/** /**
* @author Alexandre GESLIN <alexandre@gesl.in> * @author Alexandre GESLIN <alexandre@gesl.in>
* *
* @internal This class should be removed in Symfony 4.0. * @internal and will be removed in Symfony 4.0.
*/ */
class ParserCacheAdapter implements CacheItemPoolInterface class ParserCacheAdapter implements CacheItemPoolInterface
{ {

View File

@ -185,7 +185,7 @@ class ArrayChoiceList implements ChoiceListInterface
* corresponding values * corresponding values
* @param array $structuredValues The values indexed by the original keys * @param array $structuredValues The values indexed by the original keys
* *
* @internal Must not be used by user-land code * @internal
*/ */
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues) protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
{ {

View File

@ -48,7 +48,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
* *
* @return string The SHA-256 hash * @return string The SHA-256 hash
* *
* @internal Should not be used by user-land code. * @internal
*/ */
public static function generateHash($value, $namespace = '') public static function generateHash($value, $namespace = '')
{ {
@ -71,7 +71,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
* @param array $array The array to flatten * @param array $array The array to flatten
* @param array $output The flattened output * @param array $output The flattened output
* *
* @internal Should not be used by user-land code * @internal
*/ */
private static function flatten(array $array, &$output) private static function flatten(array $array, &$output)
{ {

View File

@ -30,8 +30,7 @@ use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
* *
* @see ExecutionContextInterface * @see ExecutionContextInterface
* *
* @internal You should not instantiate or use this class. Code against * @internal since version 2.5. Code against ExecutionContextInterface instead.
* {@link ExecutionContextInterface} instead.
*/ */
class ExecutionContext implements ExecutionContextInterface class ExecutionContext implements ExecutionContextInterface
{ {

View File

@ -19,8 +19,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* *
* @internal You should not instantiate or use this class. Code against * @internal version 2.5. Code against ExecutionContextFactoryInterface instead.
* {@link ExecutionContextFactoryInterface} instead.
*/ */
class ExecutionContextFactory implements ExecutionContextFactoryInterface class ExecutionContextFactory implements ExecutionContextFactoryInterface
{ {

View File

@ -22,8 +22,7 @@ use Symfony\Component\Validator\Util\PropertyPath;
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* *
* @internal You should not instantiate or use this class. Code against * @internal since version 2.5. Code against ConstraintViolationBuilderInterface instead.
* {@link ConstraintViolationBuilderInterface} instead.
*/ */
class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
{ {