8000 Support @final on methods · symfony/symfony@ae936f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit ae936f8

Browse files
committed
Support @Final on methods
1 parent b465634 commit ae936f8

File tree

6 files changed

+83
-64
lines changed

6 files changed

+83
-64
lines changed

src/Symfony/Component/Debug/DebugClassLoader.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class DebugClassLoader
2828
private $isFinder;
2929
private static $caseCheck;
3030
private static $final = array();
31+
private static $finalMethods = array();
3132
private static $deprecated = array();
3233
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
3334
private static $darwinCache = array('/' => array('/', array()));
@@ -173,6 +174,36 @@ public function loadClass($class)
173174
@trigger_error(sprintf('The %s class is considered final%s. It may change without further notice as of its next major version. You should not extend it from %s.', $parent, self::$final[$parent], $name), E_USER_DEPRECATED);
174175
}
175176

177+
// Only check final methods if the class has a parent
178+
if ($parent) {
179+
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
180+
try {
181+
$prototype = $method->getPrototype();
182+
} catch (\ReflectionException $e) {
183+
continue;
184+
}
185+
186+
$declaringClass = $prototype->getDeclaringClass()->getName();
187+
$methodName = $method->getName();
188+
if (!isset(self::$finalMethods[$declaringClass][$methodName])) {
189+
if (!isset(self::$finalMethods[$declaringClass])) {
190+
self::$finalMethods[$declaringClass] = array();
191+
}
192+
193+
if (preg_match('#\n\s+\* @final(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $prototype->getDocComment(), $notice)) {
194+
self::$finalMethods[$declaringClass][$methodName] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
195+
} else {
196+
self::$finalMethods[$declaringClass][$methodName] = false;
197+
}
198+
}
199+
200+
$finalMethod = self::$finalMethods[$declaringClass][$methodName];
201+
if (false !== $finalMethod) {
202+
@trigger_error(sprintf('The %s::%s() method is considered final%s. It may change without further notice as of its next major version. You should not extend it from %s.', $declaringClass, $methodName, $finalMethod, $name), E_USER_DEPRECATED);
203+
}
204+
}
205+
}
206+
176207
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
177208
@trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
178209
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,28 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true);
289289

290290
$this->assertSame($xError, $lastError);
291291
}
292+
293+
public function testExtendedFinalMethod()
294+
{
295+
set_error_handler(function () { return false; });
296+
$e = error_reporting(0);
297+
trigger_error('', E_USER_NOTICE);
298+
299+
class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
300+
301+
error_reporting($e);
302+
restore_error_handler();
303+
304+
$lastError = error_get_last();
305+
unset($lastError['file'], $lastError['line']);
306+
307+
$xError = array(
308+
'type' => E_USER_DEPRECATED,
309+
'message' => 'The Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod() method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod.',
310+
);
311+
312+
$this->assertSame($xError, $lastError);
313+
}
292314
}
293315

294316
class ClassLoader
@@ -324,6 +346,10 @@ public function findFile($class)
324346
return $fixtureDir.'DeprecatedInterface.php';
325347
} elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) {
326348
return $fixtureDir.'FinalClass.php';
349+
} elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) {
350+
return $fixtureDir.'FinalMethod.php';
351+
} elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) {
352+
return $fixtureDir.'ExtendedFinalMethod.php';
327353
} elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
328354
eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
329355
} elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
class ExtendedFinalMethod extends FinalMethod
6+
{
7+
/**
8+
* {@inheritdoc}
9+
*/
10+
public function finalMethod()
11+
{
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
class FinalMethod
6+
{
7+
/**
8+
* @final since version 3.3.
9+
*/
10+
public function finalMethod()
11+
{
12+
}
13+
}

src/Symfony/Component/HttpFoundation/Response.php

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -186,34 +186,6 @@ class Response
186186
511 => 'Network Authentication Required', // RFC6585
187187
);
188188

189- private static $deprecatedMethods = array(
190-
'setDate', 'getDate',
191-
'setExpires', 'getExpires',
192-
'setLastModified', 'getLastModified',
193-
'setProtocolVersion', 'getProtocolVersion',
194-
'setStatusCode', 'getStatusCode',
195-
'setCharset', 'getCharset',
196-
'setPrivate', 'setPublic',
197-
'getAge', 'getMaxAge', 'setMaxAge', 'setSharedMaxAge',
198-
'getTtl', 'setTtl', 'setClientTtl',
199-
'getEtag', 'setEtag',
200-
'hasVary', 'getVary', 'setVary',
201-
'isInvalid', 'isSuccessful', 'isRedirection',
202-
'isClientError', 'isOk', 'isForbidden',
203-
'isNotFound', 'isRedirect', 'isEmpty',
204-
'isCacheable', 'isFresh', 'isValidateable',
205-
'mustRevalidate', 'setCache', 'setNotModified',
206-
'isNotModified', 'isInformational', 'isServerError',
207-
'closeOutputBuffers', 'ensureIEOverSSLCompatibility',
208-
);
209-
private static $deprecationsTriggered = array(
210-
__CLASS__ => true,
211-
BinaryFileResponse::class => true,
212-
JsonResponse::class => true,
213-
RedirectResponse::class => true,
214-
StreamedResponse::class => true,
215-
);
216-
217189
/**
218190
* Constructor.
219191
*
@@ -229,23 +201,6 @@ public function __construct($content = '', $status = 200, $headers = array())
229201
$this->setContent($content);
230202
$this->setStatusCode($status);
231203
$this->setProtocolVersion('1.0');
232-
233-
// Deprecations
234-
$class = get_class($this);
235-
if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) {
236-
$class = get_parent_class($class);
237-
}
238-
if (isset(self::$deprecationsTriggered[$class])) {
239-
return;
240-
}
241-
242-
self::$deprecationsTriggered[$class] = true;
243-
foreach (self::$deprecatedMethods as $method) {
244-
$r = new \ReflectionMethod($class, $method);
245-
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
246-
@trigger_error(sprintf('Extending %s::%s() in %s is deprecated since version 3.2 and won\'t be supported anymore in 4.0 as it will be final.', __CLASS__, $method, $class), E_USER_DEPRECATED);
247-
}
248-
}
249204
}
250205

251206
/**

src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -843,25 +843,6 @@ public function testSettersAreChainable()
843843
}
844844
}
845845

846-
public function testNoDeprecationsAreTriggered()
847-
{
848-
new DefaultResponse();
849-
$this->getMockBuilder(Response::class)->getMock();
850-
}
851-
852-
/**
853-
* @group legacy
854-
* @expectedDeprecation Extending Symfony\Component\HttpFoundation\Response::getDate() in Symfony\Component\HttpFoundation\Tests\ExtendedResponse is deprecated %s.
855-
* @expectedDeprecation Extending Symfony\Component\HttpFoundation\Response::setLastModified() in Symfony\Component\HttpFoundation\Tests\ExtendedResponse is deprecated %s.
856-
*/
857-
public function testDeprecations()
858-
{
859-
new ExtendedResponse();
860-
861-
// Deprecations should not be triggered twice
862-
new ExtendedResponse();
863-
}
864-
865846
public function validContentProvider()
866847
{
867848
return array(

0 commit comments

Comments
 (0)
0