1

In AOP in Java (AspectJ) when we talk about method pointcuts, we can differentiate them into two different sets: method call pointcuts and method execution pointcuts.

Basing on these resources here on SO:

And some AspectJ background, we can tell that basically the differences between the two can be expressed as the following:

Given these classes:

class CallerObject {
      //...
      public void someMethod() {
         CompiletimeTypeObject target = new RuntimeTypeObject();
         target.someMethodOfTarget();
      }
      //...
}

class RuntimeTypeObject extends CompileTypeObject {
    @Override
    public void someMethodOfTarget() {
       super.someMethodOfTarget();
       //...some other stuff
    }
}

class CompiletimeTypeObject {
    public void someMethodOfTarget() {
       //...some stuff
    }
}
  • A method call pointcut refers to the call of a method from a caller object which calls the method of a target object (the one which actually implements the method being called). In the example above, the caller is CallerObject, the target is RuntimeTypeObject. Also, a method call pointcut refers to the compile time type of the object, i.e. "CompiletimeTypeObject" in the example above;

So a method call pointcut like this:

pointcut methodCallPointcut(): 
   call(void com.example.CompiletimeTypeObject.someMethodOfTarget())

Will match the target.someMethodOfTarget(); join point inside the CallerObject.someMethod() method as the compile type of the RuntimeTypeObject is CompiletimeTypeObject, but this method call pointcut:

pointcut methodCallPointcut(): 
   call(void com.example.RuntimeTypeObject.someMethodOfTarget())

Will not match, as the compile time type of the object (CompiletimeTypeObject) is not a RuntimeTypeObject or a subtype of it (it is the opposite).

  • A method execution pointcut refers to the execution of a method (i.e. after the method has been called or right before the method call returns). It doesn't give information about the caller and more important it refers to the runtime type of the object and not to the compile time type.

So, both these method execution pointcuts will match the target.someMethodOfTarget(); execution join point:

pointcut methodCallPointcut(): 
       execution(void com.example.CompiletimeTypeObject.someMethodOfTarget())

pointcut methodCallPointcut(): 
       execution(void com.example.RuntimeTypeObject.someMethodOfTarget())

As the matching is based on the runtime type of the object which is RuntimeTypeObject for both and RuntimeTypeObject is both CompiletimeTypeObject (first pointcut) and a RuntimeTypeObject (second pointcut).

Now, as PHP doesn't provide compile time types for objects (unless type-hinting is used to somehow emulate this behaviour), does it make sense to differentiate method call and method execution pointcuts in a PHP AOP implementation? How then will the pointcuts differ from each other?

Thanks for the attention!

EDIT: @kriegaex has pointed out another interesting aspect between call and method execution pointcuts in AspectJ.

Thank you for the great and concise example. I have tried to make an example myself too and here is what I understood:

In case A (I use a 3rd party library), I actually can't intercept the execution of a library method because the library itself was already compiled into bytecode and any aspect concerning that library was already woven into that bytecode too (I would need to weave the sources in order to do so).

So I can only intercept the method calls to the library methods, but again I can only intercept the calls to library methods in my code and not the calls to library methods from within the library itself because of the same principle (the calls to library methods from within the library itself are also already compiled).

The same applies for System classes (same principle) as is said here (even if the reference refers to JBoss):

https://docs.jboss.org/jbossaop/docs/2.0.0.GA/docs/aspect-framework/reference/en/html/pointcuts.html

System classes cannot be used within execution expressions because it is impossible to instrument them.

In case B (I provide a library for other users), if I actually need to intercept the usage of a method of my library either in the library itself or in the future user code which will use that method, then I need to use an execution pointcut as the aspect weaver will compile both the method execution and call pointcuts that concern my library and not the user code which will use my library methods (simply because the user code doesn't exist yet when I am writing the library), therefore using an execution pointcut will ensure that the weaving will occur inside the method execution (for a clear and intuitive example, look at the @kriegaex pseudo-code below) and not wherever the method is called within my library (i.e. at the caller side).

So I can intercept the usage (more precisely, execution) of my library method both when the method is used within my library and in the user's code. If I had used a method call pointcut in this case, I would have intercepted only the calls made from within my library, and not the calls made in the user's code.

Anyway, still think if these considerations make sense and can be applied in the PHP world, what do you think guys?

Community
  • 1
  • 1
tonix
  • 6,671
  • 13
  • 75
  • 136

2 Answers2

7

Disclaimer: I do not speak PHP, not even a little. So my answer is rather general in nature than specific to PHP.

AFAIK, PHP is an interpreted rather than a compiled language. So the difference is not compile time vs. runtime type, but semantically rather declared vs. actual type. I imagine that a PHP-based AOP framework would not "compile" anything but rather preprocess source code, injecting extra (aspect) source code into the original files. Probably it would still be possible to differentiate declared from actual types somehow.

But there is another important factor which is also relevant to the difference between call vs execution joinpoints: The place in which the code is woven. Imagine situations in which you use libraries or provide them by yourself. The question for each given situation is which parts of the source code is under the user's control when applying aspect weaving.

  • Case A: You use a 3rd party library: Let us assume you cannot (or do not want to) weave aspects into the library. Then you cannot use execution for intercepting library methods, but still use call pointcuts because the calling code is under your control.
  • Case B: You provide a library to other users: Let us assume your library should use aspects, but the library's user does not know anything about it. Then execution pointcuts will always work because the advices are already woven into your library's methods, no matter if they are called from outside or from the library itself. But call would only work for internal calls because no aspect code was woven into the user's calling code.

Only if you control the calling as well as the called (executed) code it does not make so much difference whether you use call or execution. But wait a minute, it still makes a difference: execution is just woven in one place while call it woven into potentially many places, so the amount of code generated is smaller for execution.


Update:

Here is some pseudo code, as requested:

Let us assume we have a class MyClass which is to be aspect-enhanced (via source code insertion):

class MyClass {
    method foo() {
        print("foo");
        bar();
    }

    method bar() {
        print("bar");
        zot();
    }

    method zot() {
        print("zot");
    }

    static method main() {
        new McClass().foo();
    }
}

Now if we apply a CallAspect like this using call()

aspect CallAspect {
    before() : call(* *(..)) {
        print("before " + thisJoinPoint);
    }
}

upon our code, it would look like this after source code weaving:

class MyClass {
    method foo() {
        print("foo");
        print("before call(MyClass.bar())");
        bar();
    }

    method bar() {
        print("bar");
        print("before call(MyClass.zot())");
        zot();
    }

    method zot() {
        print("zot");
    }

    static method main() {
        print("before call(MyClass.foo())");
        new McClass().foo();
    }
}

Alternatively, if we apply an ExecutionAspect like this using execution()

aspect ExecutionAspect {
    before() : execution(* *(..)) {
        print("before " + thisJoinPoint);
    }
}

upon our code, it would look like this after source code weaving:

class MyClass {
    method foo() {
        print("before execution(MyClass.foo())");
        print("foo");
        bar();
    }

    method bar() {
        print("before execution(MyClass.bar())");
        print("bar");
        zot();
    }

    method zot() {
        print("before execution(MyClass.zot())");
        print("zot");
    }

    static method main() {
        print("before execution(MyClass.main())");
        new McClass().foo();
    }
}

Can you see the difference now? Pay attention to where the code is woven into and what the print statements say.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • But doesn't that work only if you use something like cflow() together with call()? Suppose that the `void com.example.CompiletimeTypeObject.someMethodOfTarget()` method is called inside the 3rd party library, given a method call pointcut like the one in my example, it will be triggered too, won't it? Please, could you write a pseudo-code example of the two cases? – tonix Jan 31 '15 at 16:36
  • I do not understand your question. Instead of me writing something in (non-compileable) pseudo code, I recommend you to test what I explained on some real code in AspectJ. It should be clear from my explanation anyway, I think, but seeing is believing. If you want to write your own AOP framework (not a simple feat), you should get some more experience with an existing one. The difference between `call` and `execution` is really fundamental. – kriegaex Jan 31 '15 at 18:13
  • 1
    I have just slightly rephrased cases A and B to make the explanations more precise. Maybe this helps. – kriegaex Jan 31 '15 at 18:19
  • You can compile PHP, as Facebook does: http://stackoverflow.com/questions/1408417/can-you-compile-php-code – James Black Jan 31 '15 at 18:24
  • @kriegaex You say in the first case that if I use a 3rd party library and I can't or do not want to weave my aspects into the library then I can use call pointcuts instead of execution ones. What do you mean with `calling code is under your control` and `you cannot use execution for intercepting library methods`? That I can't use execution for intercepting cause they are already woven when the library was compiled? Then in the second case, I shouldn't use call pointcuts because if I compile the library the call pointcuts will only match the method calls **in my library** and not in user code? – tonix Jan 31 '15 at 19:02
  • In my first question what I was trying to say was: a method like `com.example.SomeLibraryObject.someMethod()` is called inside a 3rd party library. I need to weave it with an aspect, I can make a call pointcut `pointcut aCallPointcut(): call(com.example.SomeLibraryObject.someMethod())` and the pointcut will be triggered, right? But then if I use an execution pointcut `pointcut anExecutionPointcut(): execution(com.example.SomeLibraryObject.someMethod())`, it will match too, right? – tonix Jan 31 '15 at 19:12
  • @JamesBlack: Compiling PHP is not the point here, but thanks. – kriegaex Jan 31 '15 at 19:13
  • 1
    @user3019105: Yes, if the target method is inside a 3rd party library or just inside a directory which was not subject to aspect weaving, you can only intercept methods therein when using `call`. In order to be able to use `execution` the target method itself must have been woven. It is actually very simple, just give it a try in AspectJ. – kriegaex Jan 31 '15 at 19:17
  • 1
    I have added some (Java-like) pseudo code showing what our hypothetical source code weaver would insert into the code. Please check the update. – kriegaex Jan 31 '15 at 19:34
  • @kriegaex Thank you for the example! I have tried to reproduce what you mean too, please, could you take a look at my edit too? Are my statements correct now? – tonix Jan 31 '15 at 20:52
  • 1
    Yes, they are basically right. But I suggest we stop talking now and close the discussion because StackOverflow is not a forum but a platform for solving concrete, programming-related problems. If you like my answer, feel free to accept and upvote. Otherwise, good luck for implementing your PHP-based AOP framework, or just perform a [web search](https://www.google.com/webhp?&gws_rd=cr&ei=zFHNVNohi4I9hsyAyAo#safe=off&q=php+aop). – kriegaex Jan 31 '15 at 22:07
  • Thank you for you explanations and for having made the example! I have accepted your answer, anyway I'd like to hear some opinions from others too, especially those from the PHP world! – tonix Jan 31 '15 at 23:17
  • Primarily opinion-based discussion is forbidden here, you risk that others vote to close the question. We have discussed too much already. ;-) – kriegaex Jan 31 '15 at 23:32
  • @kriegaex All right, thank you for pointing me this. And thanks again for the explanations! – tonix Feb 02 '15 at 19:16
  • 1
    @kriegaex about the CallAspect module, the before advice also selects all the calls of the print("") method as well not just foo, bar and zot. you maybe wanted to mention those as comments though. – Hakan Özler Feb 03 '15 at 09:15
  • @Hakan print("") calls but inside the *user's code*, like in the example (i.e. only in the MyClass class), right? Not any other print e.g. in a third party library or in a system class. – tonix Feb 03 '15 at 09:36
  • the `call(* *(..))` signature selects any method calls in a system including the print call in the body of the before advice actually (i.e. `print("before " + thisJoinPoint);`). If you run this example along with the `MyClass` class, StackOverflowError will be given eventually. You need to narrow down the scope of this before advice by putting this pointcut : `within(MyClass)` next to call() pointcut and then you will not receive any error. – Hakan Özler Feb 03 '15 at 12:15
  • What was unclear about the term pseudo code? I wanted to concentrate on the explanation of the problem at hand because the question was about another language, not AspectJ. If you read dozens of my other answers, you see that usually I give a complete, compileable example including console output. This time I did not - on purpose. I wanted this answer *not* to be about AspectJ. But if you absolutely want to run it in a JVM, I recommend to use `!within(ExecutionAspect)` instead of limiting the pointcut to one class. – kriegaex Feb 04 '15 at 22:12
1

PHP is dynamic language, so it's quite hard to implement call joinpoints because there are many languages features like call_user_func_array(), $func = 'var_dump'; $func($func);

@kriegaex wrote a good answer with main differences between call and execution types of joinpoints. Applying to the PHP, only possible joinpoint for now is an execution joinpoint, because it's much easier to hook execution of method|function by wrapping a class with decorator or by providing PHP extension for that.

Actually, Go! AOP framework provides only execution joinpoint, as well, as FLOW3 framework and others.

lisachenko
  • 5,952
  • 3
  • 31
  • 51
  • Thanks for your considerations concerning PHP! Your implementation in **Go! AOP** is really interesting. Just let me ask you a question, as far as I understood, Go! weaves aspects on classes when they're autoloaded, am I right? Basically, you intercept the `autoloading` before any other `spl_autoload_register` and use static analysis thanks to PHP Token Reflection, create a '*proxied/decorated*' mirror of the woven class (if it matches the aspects' rules, of course), then extend this proxy with a class having the same name of the original one? But how then you obey to Liskov's SP? – tonix Feb 02 '15 at 19:15
  • 1
    @user3019105 yes, you are right about logic of weaving and method interception technique in the framework. Question about LSP is excellent. LSP *can* be violated in rare cases when `self` is used for typehints instead of `static`, but on the other side, I just replace an original class with child and thanks to LSP this works in 99% percents. – lisachenko Feb 03 '15 at 06:42
  • Thanks to `static` you allow overriding parent static methods, but as Go! can intercept even final classes and methods (static or not) I can only think that you weave on a target depending on its structure, if it is not final, you extend it, otherwise you create a proxied class of it. But if so, in the last case, if I have a DI container and I type hint a method e.g. `someMethod(SomeType $obj)`, after weaving, will `SomeTypeProxy` be passed as $obj? This violates LSP, and of course causes a `Catchable Fatal Error`, so how do you guarantee interoperability with the container in such a case? – tonix Feb 03 '15 at 10:31
  • 1
    Go! AOP always extends original class, even final :) If class is final then framework just removes "final" keyword from the original class and move it to the decorator generated code. So class is still final from user point of view, but it's already baked with extended functionality. No `SomeTypeProxy` classes, no LSP violation, `SomeType` will be used all the time. – lisachenko Feb 04 '15 at 11:40
  • Ingenious! Thank you for the explanation! – tonix Feb 04 '15 at 15:46