-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Debug] Support @final
on methods
#21465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
847f7b2
to
ae936f8
Compare
if ($parent) { | ||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { | ||
try { | ||
$prototype = $method->getPrototype(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is getPrototype
needed? (I'm not very familiar with it :) )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I discovered it today, it is fetching the parent method, which is what we need, we could do that manually but we would have to support several edge cases (for instance a method of the parent of the parent extended).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GuilhemN Very cool find. I've needed something similar in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice finding.
Then I'm wondering if we need it at all :)
Parent classes are always loaded before their children. This means each class should only look at methods that have $method->class === $class
(let's use properties as much as possible, performance is critical here).
By populating the self::$finalMethods
props, then children should have everything to decide quickly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolas-grekas we would have to support the edge case I described above and maybe others i didn't think about, we would also have to parse every method doc blocks which is bad for perfs, imo it is better this way.
This means each class should only look at methods that have $method->class === $class
It looks like I forgot this check...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parsing the docblock (doing an strpos preflight really) should be faster than trying getPrototype, then fall in the catch block, which involves instanciating a ReflectionException behind the scene - especially since this situation is going to happen quite often (since many methods don't have a "protototype")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it and the code is clearer too so 👍
a2e83ce
to
ea53d22
Compare
|
||
if ($parent && isset(self::$finalMethods[$parent][$method->name])) { | ||
// Fetch the real class declaring the parent method | ||
$prototype = $method->getPrototype(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you don't need this call if you compute the message when populating self::$finalMethods[$name][$method->name]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍
} | ||
|
||
$doc = $method->getDocComment(); | ||
// No @final annotation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code says it :)
self::$finalMethods[$name] = $parent && isset(self::$finalMethods[$parent]) ? self::$finalMethods[$parent] : array(); | ||
|
||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { | ||
// It's not the declaring class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ueless to me :)
@final
on methods
|
||
$xError = array( | ||
'type' => E_USER_DEPRECATED, | ||
'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.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be possible to turn this into an @expectedDeprecation
annotation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I just followed the rest of the file, I guess it was done to not add the legacy
group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed!
f3bb16a
to
d1c9d49
Compare
@@ -173,6 +174,32 @@ public function loadClass($class) | |||
@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); | |||
} | |||
|
|||
// Not an interface nor a trait | |||
if (class_exists($name, false)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should also apply to @final
on the class above, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but I think we should wait for this to be merged before doing it, to avoid conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact it's not a big deal, I can just rebase this PR if we do it in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
d1c9d49
to
d1e0588
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thank you @GuilhemN. |
This PR was squashed before being merged into the 3.3-dev branch (closes #21465). Discussion ---------- [Debug] Support `@final` on methods | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #21461 (comment) | License | MIT | Doc PR | This adds support for `@final` on methods: ```php class Foo { /** * @Final since version 2.0. */ public function bar() { } } ``` ping @nicolas-grekas Commits ------- 1b0a6b6 [Debug] Support on methods
This PR was merged into the 3.3-dev branch. Discussion ---------- [HttpFoundation] Fix bad merge | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Bad merge, code had been removed in #21465 Commits ------- af1eed9 [HttpFoundation] Fix bad merge
This adds support for
@final
on methods:ping @nicolas-grekas