-
-
Notifications
You must be signed in to change notification settings - Fork 163
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
Exclude abstract methods from pointcut matching #335 #337
Exclude abstract methods from pointcut matching #335 #337
Conversation
/** | ||
* Pointcut that filters out all class method and properties which are not declared within class | ||
*/ | ||
class ClassDeclaresPointcut implements Pointcut |
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.
Idea is to have a pointcut matcher that will filter out all non-declared members and properties that are matched.
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.
Looks like it is already implemented in MatchInheritedPointcut
😄
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, so it seams....
*/ | ||
public function getKind() | ||
{ | ||
return PointFilter::KIND_METHOD | PointFilter::KIND_PROPERTY; |
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.
As I have understood - this will ensure that this pointcut matcher will be applied ONLY if subject of matching is class method and class property, right?
Otherwise, matches
function will not be invoked, right?
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, you are right, point filter effectively reduce the number of checks for concrete point filter. It's not ideal, but it's very efficient. KIND_METHOD
filters only methods, KIND_PROPERTY
filters properties in the class.
For future versions of framework I want to put this logic into matches()
method. To be in one single place.
src/Aop/Pointcut/PointcutParser.php
Outdated
*/ | ||
public function parse(TokenStream $stream) | ||
{ | ||
return new AndPointcut(parent::parse($stream), new ClassDeclaresPointcut()); |
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.
Ok, so this is why I think that this solution is kinda hackish...
Whatever our parser gets as a pointcut filter, it has to be additionally filtered. So it has to be applied last.
parent::parse($stream)
-> will provide a pointcut that will match something, and that something can be a collection of properties and methods, right?
So, whatever methods and properties we got, we have to apply ClassDeclaresPointcut
matcher to filter out non-declared properties and methods.
I guess that Grammar should give us this expression, is that even possible?
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.
Oh, crap, I forgot that we can add a node visitor that would modify AST and provide us with same result: https://github.com/jakubledl/dissect/blob/master/docs/ast.md#traversing-the-ast
I can write node visitor to modify AST in order to move this logic from PointcutParser to node visitor and grammar.
However - still need from you the verification of idea so I can proceed (write node visitor, tests, etc...)
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.
Ok, seams wrong -> I should only wrap original Pointcut if getKind()
is PROPERTY or METHOD, right?
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.
You shouldn't touch the PointcutParser
class at all, it's final :) Instead, all changes should be implemented in grammar and AST transforming, see PointcutGrammar
class for examples how AST will be converted to the pointcut.
Hi, Nikola! Thank you for researching this! But correct me if I'm wrong: you want to exclude all parent methods that aren't declared in current class, right? If it's what you want to achieve, then there is special syntax for that: // This will match all methods, but not inherited
['execution(public **->*(*)) && !matchInherited()'], |
Yes. Ok, I got confused here for sure - why Why would weaving of method/property occur on a class that does not declares it? |
Ok, this is the issue of understanding what is the bug and what is not. This proposal is related to #335. Lack of declaration existence check leads to issue #335 For me - there is no logic to weave method/property that is not declared within the class. If there is - I believe that it should be explicitly requested by pointcut expression, ie. This was my usecase:
Booth Z and Y classes are weaved. It should be only Z. And that should be by default, right? |
Previously developers asked me why there is no matching for parent methods, issue about matching for parent methods was described in #131 and later I was implemented logic to match parent properties and methods, see #272 and #274. New logic was introduced in PR #279. This was logical for me to match methods from parent classes during weaving. And filtration should be performed via pointcut definition. Regarding your question about switching matching to declared members only be default - it's discussable. Maybe we can introduce new option for the kernel to allow/disable matching with parent members for classes? |
Ok, THANK YOU - now we are getting somewhere. In conclusion, we can not change this behaviour, it would result with BC break. Conclusion is: weaving will occur always on all matching methods/properties, regardless if it is defined in class or some of the parent class (unless Ok, so we have a bug, that is for sure, which is easy to define now:
Simple - what is not implemented in class hierarchy, can not be weaved. Agree? (if we can identify bug and agree upon what is a bug - we can solve it) |
Agree
Actually, it's the same as the first check, because method from an interface will be abstract too in reflection. So resulting statement will be: "Pointcut MUST NOT match abstract methods" |
@@ -47,6 +47,7 @@ class ModifierMatcherFilter implements PointFilter | |||
public function __construct($initialMask = 0) | |||
{ | |||
$this->andMask = $initialMask; | |||
$this->notMask = \ReflectionMethod::IS_ABSTRACT; |
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.
@lisachenko Ok, it seams that you are right, modifier filter should have default value of \ReflectionMethod::IS_ABSTRACT for notMask
.
Test passes, but what is worries me is that there are no a lot of integration/functional tests where we can test what we actually get (like what I did for console commands).
What do you think about writing some functional tests that would have real-life fixtures for verification of weaving?
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 feel myself shame for tests :) I'd vote for unit tests for each class and only several functional tests for checking that whole system is working as expected and still able to transform files and perform weaving.
For this concrete class we can add just simple test case that modifier matcher filter by default doesn't match abstract methods.
One more caution: modifier filter is also used for property matching. Of course, "not abstract" for properties will be always true. But if we will use ModiferFilter for properties with negation, then it can be very unpredictable :)
@@ -166,6 +166,11 @@ private function getAdvicesFromAdvisor( | |||
|
|||
$mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED; | |||
foreach ($class->getMethods($mask) as $method) { | |||
// abstract methods can not be weaved |
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.
One more caution: modifier filter is also used for property matching. Of course, "not abstract" for properties will be always true. But if we will use ModiferFilter for properties with negation, then it can be very unpredictable :)
You are soooo right! Ok, how about this? Simply - advice matcher will skip abstract methods for weaving, regardless of pointcut expression used?
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 solution is perfect! 👍
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.
Looking good now
|
||
abstract class AbstractBar implements FooInterface | ||
{ | ||
public abstract function doSomethingElse(); |
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 abstract declaration must precede the visibility declaration
|
||
protected static function exec($command, $args = '') | ||
{ | ||
$process = new Process(sprintf('php %s %s %s %s', |
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.
Opening parenthesis of a multi-line function call must be the last content on the line
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.
Better to follow PSR here and make it one-liner or extract sprintf part of expression into separate variable.
use Symfony\Component\Filesystem\Filesystem; | ||
use Symfony\Component\Process\Process; | ||
|
||
abstract class BaseFunctionalTest extends TestCase |
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.
@lisachenko When you find a time, please review tests as well.
I have cleaned up code a little bit, setup a base for functional tests, write a required test as well as modified (cleaned up) command tests...
I have one more thing on my mind, however, even with this setup it is going to be quite easy to write a bunch of functional tests that we need.
|
||
use Go\Aop\Aspect; | ||
use Go\Aop\Intercept\MethodInvocation; | ||
use Go\Lang\Annotation as Pointcut; |
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 import of namespace name
protected static $projectDir = __DIR__.'/../../Fixtures/project'; | ||
protected static $aspectCacheDir = __DIR__.'/../../Fixtures/project/var/cache/aspect'; | ||
protected static $consolePath = __DIR__.'/../../Fixtures/project/bin/console'; | ||
protected static $frontControllerPath = __DIR__.'/../../Fixtures/project/web/index.php'; |
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'd prefer to have these assignments aligned... It will be more readable.
Can we also replace these static properties with simple class properties?
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.
Not recommended at all. In order to be compatible with static calls, such as public static function setUpBeforeClass()
they must be static, that is, must be accessible in static context.
Best thing what I can do is to define them as constants, if you prefer that more.
|
||
protected static function exec($command, $args = '') | ||
{ | ||
$process = new Process(sprintf('php %s %s %s %s', |
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.
Better to follow PSR here and make it one-liner or extract sprintf part of expression into separate variable.
Oops, merged without squashing changes. Going to clean manually... |
Merged into master as well. Added |
This is a first, working idea. I guess there is a better approach. There are no tests written yet, however, I have tested internally and this do fixes my problem that I have described.
Solution seams un-natural and hackish. I will explain in code general idea.
@lisachenko I would like from you to review it and propose a better/more elegant solution for this fix implementation.
Thanks.