[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>
*
* @internal This class is meant for internal use only.
* @internal
*/
class IdReader
{

View File

@ -27,6 +27,7 @@ class DebugClassLoader
private $classLoader;
private $isFinder;
private static $caseCheck;
private static $internal = array();
private static $final = array();
private static $finalMethods = array();
private static $deprecated = array();
@ -166,10 +167,14 @@ class DebugClassLoader
}
$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
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]) : '';
}
@ -203,50 +208,57 @@ class DebugClassLoader
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);
} 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]);
}
// 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 {
// 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 {
switch ($ns = substr($name, 0, $len)) {
case 'Symfony\Bridge\\':
case 'Symfony\Bundle\\':
case 'Symfony\Component\\':
$ns = 'Symfony\\';
$len = strlen($ns);
break;
switch ($ns = substr($name, 0, $len)) {
case 'Symfony\Bridge\\':
case 'Symfony\Bundle\\':
case 'Symfony\Component\\':
$ns = 'Symfony\\';
$len = strlen($ns);
break;
}
}
foreach (array_merge(array($parent), class_implements($name, false), class_uses($name, false)) as $use) {
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)) {
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);
foreach ($refl->getInterfaceNames() as $interface) {
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
$deprecatedInterfaces[] = $interface;
}
$parentInterfaces = array();
$deprecatedInterfaces = array();
if ($parent) {
foreach (class_implements($parent) as $interface) {
$parentInterfaces[$interface] = 1;
}
foreach (class_implements($interface) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
foreach ($refl->getInterfaceNames() as $interface) {
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
$deprecatedInterfaces[] = $interface;
}
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);
}
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);
}
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
@ -335,22 +353,12 @@ class ClassLoader
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return $fixtureDir.'CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return $fixtureDir.'reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
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) {
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
@ -363,6 +371,10 @@ class ClassLoader
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
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>
*
* @internal This class should be removed in Symfony 4.0.
* @internal and will be removed in Symfony 4.0.
*/
class ParserCacheAdapter implements CacheItemPoolInterface
{

View File

@ -185,7 +185,7 @@ class ArrayChoiceList implements ChoiceListInterface
* corresponding values
* @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)
{

View File

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

View File

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

View File

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

View File

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