Skip to content

Commit 0effd27

Browse files
feature #23807 [Debug] Trigger a deprecation when using an internal class/trait/interface (GuilhemN)
This PR was squashed before being merged into the 3.4 branch (closes #23807). Discussion ---------- [Debug] Trigger a deprecation when using an internal class/trait/interface | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes <!-- don't forget updating src/**/CHANGELOG.md files --> | BC breaks? | no | Deprecations? | not truly <!-- don't forget updating UPGRADE-*.md files --> | Tests pass? | yes | Fixed tickets | #23800 | License | MIT | Doc PR | Trigger a deprecation when the user uses an internal class/trait/interface. The deprecation is not triggered when an `@internal` is used in the same vendor. Commits ------- b89ba29 [Debug] Trigger a deprecation when using an internal class/trait/interface
2 parents df78a73 + b89ba29 commit 0effd27

File tree

13 files changed

+119
-57
lines changed

13 files changed

+119
-57
lines changed

src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @author Bernhard Schussek <bschussek@gmail.com>
2222
*
23-
* @internal This class is meant for internal use only.
23+
* @internal
2424
*/
2525
class IdReader
2626
{

src/Symfony/Component/Debug/DebugClassLoader.php

+48-36
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class DebugClassLoader
2727
private $classLoader;
2828
private $isFinder;
2929
private static $caseCheck;
30+
private static $internal = array();
3031
private static $final = array();
3132
private static $finalMethods = array();
3233
private static $deprecated = array();
@@ -166,10 +167,14 @@ public function loadClass($class)
166167
}
167168

168169
$parent = get_parent_class($class);
170+
$doc = $refl->getDocComment();
171+
if (preg_match('#\n \* @internal(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
172+
self::$internal[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
173+
}
169174

170175
// Not an interface nor a trait
171176
if (class_exists($name, false)) {
172-
if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
177+
if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
173178
self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
174179
}
175180

@@ -203,50 +208,57 @@ public function loadClass($class)
203208

204209
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
205210
@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);
206-
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
211+
}
212+
if (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
207213
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
214+
}
215+
216+
// Don't trigger deprecations for classes in the same vendor
217+
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
218+
$len = 0;
219+
$ns = '';
208220
} else {
209-
// Don't trigger deprecations for classes in the same vendor
210-
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
211-
$len = 0;
212-
$ns = '';
213-
} else {
214-
switch ($ns = substr($name, 0, $len)) {
215-
case 'Symfony\Bridge\\':
216-
case 'Symfony\Bundle\\':
217-
case 'Symfony\Component\\':
218-
$ns = 'Symfony\\';
219-
$len = strlen($ns);
220-
break;
221-
}
221+
switch ($ns = substr($name, 0, $len)) {
222+
case 'Symfony\Bridge\\':
223+
case 'Symfony\Bundle\\':
224+
case 'Symfony\Component\\':
225+
$ns = 'Symfony\\';
226+
$len = strlen($ns);
227+
break;
222228
}
229+
}
223230

224-
if (!$parent || strncmp($ns, $parent, $len)) {
225-
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
226-
@trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
227-
}
231+
foreach (array_merge(array($parent), class_implements($name, false), class_uses($name, false)) as $use) {
232+
if (isset(self::$internal[$use]) && strncmp($ns, $use, $len)) {
233+
@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);
234+
}
235+
}
228236

229-
$parentInterfaces = array();
230-
$deprecatedInterfaces = array();
231-
if ($parent) {
232-
foreach (class_implements($parent) as $interface) {
233-
$parentInterfaces[$interface] = 1;
234-
}
237+
if (!$parent || strncmp($ns, $parent, $len)) {
238+
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
239+
@trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
240+
}
241+
242+
$parentInterfaces = array();
243+
$deprecatedInterfaces = array();
244+
if ($parent) {
245+
foreach (class_implements($parent) as $interface) {
246+
$parentInterfaces[$interface] = 1;
235247
}
248+
}
236249

237-
foreach ($refl->getInterfaceNames() as $interface) {
238-
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
239-
$deprecatedInterfaces[] = $interface;
240-
}
241-
foreach (class_implements($interface) as $interface) {
242-
$parentInterfaces[$interface] = 1;
243-
}
250+
foreach ($refl->getInterfaceNames() as $interface) {
251+
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
252+
$deprecatedInterfaces[] = $interface;
253+
}
254+
foreach (class_implements($interface) as $interface) {
255+
$parentInterfaces[$interface] = 1;
244256
}
257+
}
245258

246-
foreach ($deprecatedInterfaces as $interface) {
247-
if (!isset($parentInterfaces[$interface])) {
248-
@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);
249-
}
259+
foreach ($deprecatedInterfaces as $interface) {
260+
if (!isset($parentInterfaces[$interface])) {
261+
@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);
250262
}
251263
}
252264
}

src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php

+22-10
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,24 @@ class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
312312

313313
$this->assertSame($xError, $lastError);
314314
}
315+
316+
public function testInternalsUse()
317+
{
318+
$deprecations = array();
319+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
320+
$e = error_reporting(E_USER_DEPRECATED);
321+
322+
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
323+
324+
error_reporting($e);
325+
restore_error_handler();
326+
327+
$this->assertSame($deprecations, array(
328+
'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".',
329+
'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".',
330+
'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".',
331+
));
332+
}
315333
}
316334

317335
class ClassLoader
@@ -335,22 +353,12 @@ public function findFile($class)
335353
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
336354
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
337355
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
338-
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
339-
return $fixtureDir.'CaseMismatch.php';
340356
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
341357
return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
342358
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
343359
return $fixtureDir.'reallyNotPsr0.php';
344360
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
345361
return $fixtureDir.'notPsr0Bis.php';
346-
} elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) {
347-
return $fixtureDir.'DeprecatedInterface.php';
348-
} elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) {
349-
return $fixtureDir.'FinalClass.php';
350-
} elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) {
351-
return $fixtureDir.'FinalMethod.php';
352-
} elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) {
353-
return $fixtureDir.'ExtendedFinalMethod.php';
354362
} elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
355363
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
356364
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
@@ -363,6 +371,10 @@ public function findFile($class)
363371
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
364372
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
365373
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
374+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
375+
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface {
376+
use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
377+
}');
366378
}
367379
}
368380
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @internal since version 3.4.
7+
*/
8+
class InternalClass
9+
{
10+
use InternalTrait2;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @internal
7+
*/
8+
interface InternalInterface
9+
{
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @internal
7+
*/
8+
trait InternalTrait
9+
{
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @internal
7+
*/
8+
trait InternalTrait2
9+
{
10+
}

src/Symfony/Component/ExpressionLanguage/ParserCache/ParserCacheAdapter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/**
1919
* @author Alexandre GESLIN <alexandre@gesl.in>
2020
*
21-
* @internal This class should be removed in Symfony 4.0.
21+
* @internal and will be removed in Symfony 4.0.
2222
*/
2323
class ParserCacheAdapter implements CacheItemPoolInterface
2424
{

src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public function getValuesForChoices(array $choices)
185185
* corresponding values
186186
* @param array $structuredValues The values indexed by the original keys
187187
*
188-
* @internal Must not be used by user-land code
188+
* @internal
189189
*/
190190
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
191191
{

src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
4848
*
4949
* @return string The SHA-256 hash
5050
*
51-
* @internal Should not be used by user-land code.
51+
* @internal
5252
*/
5353
public static function generateHash($value, $namespace = '')
5454
{
@@ -71,7 +71,7 @@ public static function generateHash($value, $namespace = '')
7171
* @param array $array The array to flatten
7272
* @param array $output The flattened output
7373
*
74-
* @internal Should not be used by user-land code
74+
* @internal
7575
*/
7676
private static function flatten(array $array, &$output)
7777
{

src/Symfony/Component/Validator/Context/ExecutionContext.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
*
3131
* @see ExecutionContextInterface
3232
*
33-
* @internal You should not instantiate or use this class. Code against
34-
* {@link ExecutionContextInterface} instead.
33+
* @internal since version 2.5. Code against ExecutionContextInterface instead.
3534
*/
3635
class ExecutionContext implements ExecutionContextInterface
3736
{

src/Symfony/Component/Validator/Context/ExecutionContextFactory.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
*
2020
* @author Bernhard Schussek <bschussek@gmail.com>
2121
*
22-
* @internal You should not instantiate or use this class. Code against
23-
* {@link ExecutionContextFactoryInterface} instead.
22+
* @internal version 2.5. Code against ExecutionContextFactoryInterface instead.
2423
*/
2524
class ExecutionContextFactory implements ExecutionContextFactoryInterface
2625
{

src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
*
2323
* @author Bernhard Schussek <bschussek@gmail.com>
2424
*
25-
* @internal You should not instantiate or use this class. Code against
26-
* {@link ConstraintViolationBuilderInterface} instead.
25+
* @internal since version 2.5. Code against ConstraintViolationBuilderInterface instead.
2726
*/
2827
class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
2928
{

0 commit comments

Comments
 (0)