Skip to content
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

[Feature][Long-Term] Interception of methods in the internal PHP classes #188

Open
lisachenko opened this issue Dec 2, 2014 · 26 comments
Open

Comments

@lisachenko
Copy link
Member

Interception of methods in the internal PHP classes is the most complex one, however, it is required for special cases, for example, to intercept mysql->query() invocation:

class MysqliQueryOperationCollectionAspect implements Aspect {

    /**
     * @param MethodInvocation $invocation Invocation
     * @Around(execution(public mysqli->query(*))
     * @return mixed
     */
    public function aroundQueryMethod(MethodInvocation $invocation) {
        $method = $invocation->getMethod();
        $args = $invocation->getArguments();
        $query = $args[1];
        if ($this->logger->isTraceEnabled()) {
            $this->logger->trace($method->getName().'('.$query.')');
        }

        try {
            $result = $invocation->proceed();
            return $result;
        } catch(Exception $e) {
            if ($this->logger->isTraceEnabled()) {
                $this->logger->trace($method->getName().'('.$query.') '.get_class($e).':' .$e->getMessage());
            }
            throw $e;
        }
    }
}
@lgoldstein
Copy link

On a related issue - I have read the Intercepting Execution of System Functions in PHP article and was wondering if the above limitation also applies to core functions (e.g., mysql_query(...)). In other words, can I use Go! to intercept this kind of functions ? If not, then while you are considering the core classes issue, can you also consider the core functions one as well ?

@lisachenko
Copy link
Member Author

@lgoldstein you can intercept core functions with Go! AOP, but cache warming will be slower. Check the demo: http://demo.aopphp.com/?showcase=function-interceptor

UPD1: To enable a function interception, you should explicitly enable this functionality via Features::INTERCEPT_FUNCTIONS, see https://github.com/lisachenko/go-aop-php/blob/master/demos/autoload_aspect.php#L24 and then just define an advice with execution(Some\Namespace\func_name(*)), see https://github.com/lisachenko/go-aop-php/blob/master/demos/Demo/Aspect/FunctionInterceptorAspect.php#L23-L32

@lgoldstein
Copy link

@lisachenko Great - I will give it a go with mysql functions...

@lisachenko
Copy link
Member Author

Updated my previous answer to include some useful links for you, should be helpful.

@lgoldstein
Copy link

Please note that I am talking about core functions (like mysql_query()) - all your examples refer to user functions in some application namespace - this is not the case for core functions...

@lisachenko
Copy link
Member Author

My example is for core functions :) so, if you have a call to the mysqli_query(), then you can intercept it. There are several requirements: function call should be from a namespaced code (won't work for the global namespace), function call should not use backslash like this \mysqli_query()

Typical modern code is satisfies these requirements.

@lgoldstein
Copy link

@lisachenko Great - just wanted to make sure that you understood my meaning. I haven't tried it yet (turns out the application I was trying to instrument uses either mysqli or PDO). I will try to locate an application that uses mysql funtioncs and see if I can instrument it.

BTW, I assume that PDO is also non-instrumentable (being a core class...)...

@lisachenko
Copy link
Member Author

Yes, PDO are core classes, so without this feature, framework can't handle them.

@lgoldstein
Copy link

@lisachenko Understood - I will follow this issue hoping that a solution can be found...

@lgoldstein
Copy link

@lisachenko Does not seem to work for mysql_query functions - the code is issuing queries but the aspect isn't being invoked. I have used this sample application with the following initializations and code:

 $aspectKernel = VMerlinAspectKernel::getInstance();
    // see AspectKernel::getDefaultOptions
    $aspectKernel->init(array(
            'debug' => false,
            // Cache directory
            'cacheDir' => $cacheDir,
            'appDir' => join(DIRECTORY_SEPARATOR, array('C:', 'Projects', 'appwatch', 'xampp', 'htdocs', 'phpcrud')),
            'interceptFunctions' => true
        ));

Followed by this aspect definition (the aspect is registered at the container in the VMerlinAspectKernel::configureAop function):

/**
 * Intercepts <U>functional</U> calls to <code>mysql_*query*</code>
 * @author Lyor Goldstein <[email protected]>
 * @since Dec 3, 2014 2:57:43 PM
 */
class MysqlFunctionsOperationCollectionAspect extends VMerlinOperationCollectionAspectSupport implements Aspect {
    function __construct(array $config) {
        parent::__construct($config);
    }   
    /**
     * @param FunctionInvocation  $invocation Invocation
     * @Around(execution(mysql_query(*))
     * @return mixed
     */
    public function aroundQueryFunctions(FunctionInvocation $invocation) {
        $method = $invocation->getFunction();
        $args = $invocation->getArguments();
        $query = $args[0];
        if ($this->logger->isTraceEnabled()) {
            $this->logger->trace($method->getName().'('.$query.')');
        }

        $startTime = VMerlinHelper::timestampValue();
        try {
            $result = $invocation->proceed();
            return $result;
        } catch(Exception $e) {
            if ($this->logger->isTraceEnabled()) {
                $this->logger->trace($method->getName().'('.$query.') '.get_class($e).': '.$e->getMessage());
            }
            throw $e;
        }
    }
}

@lisachenko
Copy link
Member Author

@lgoldstein your pointcut will never match anything, correct form is @Around(execution(**\mysql_query(*)) - execution of system mysql_query in all namespaces.

But it's only a minor fix. Framework is not suited for spaghetti code like in your example. http://zaldivar.orgfree.com/php/crud-example-for-php-beginner/ This will result in errors during static parsing and analysing of source code.

Moreover, auto_prepending AOP initalization for such spaghetti code won't do anything. So, is your real source code looks like that? Or there are normal classes with methods?

For auto_prepended file there is only one ugly way to enable AOP, something like this: eval(file_get_contents(FilterInjectorTransformer::rewrite($_SERVER['SCRIPT_FILENAME']));die; But I don't like it at all :)

@lgoldstein
Copy link

@lisachenko My purpose is to use Go! in order to instrument any application - in other words, I want to place some code in the auto_prepend PHP script that will intercept API(s) of interest (e.g., mysql). Therefore, I have no control over the "spaghetti code" that you mention - i.e., I am not instrumenting my application but rather any application - one that is written by anyone (so I don't know if they are using "nice" or "spaghetti" code). Furthermore, I don't want to ask the user to change his/her application scripts with ugly code such as the eval statement you mentioned.

In this context, if the application I am trying to instrument contains only pure PHP instead of mixed HTML and PHP would the auto_prepend approach work ? Is there some way to have Go! support mixed HTML + PHP scripts (such as the example) ?

@lisachenko
Copy link
Member Author

@lgoldstein nice questions, you are right that tool should work for any application. Unfortunately, PHP-Token-Reflection library (which is used internally by framework) may not be able to parse any code, because it's assume that code is valid OOP, eg. classes, functions, interfaces, etc.

I think, that userland implementation of AOP is not suitable for your requirements. Have you tried AOP-PHP extension? This tool is working on engine level and you can use it with auto_prepend as well, also it capable to intercept execution of system functions and execution of methods for internal classes. But be aware, that this project is not maintained for 6 monthes and doesn't working for PHP>=5.5

@lgoldstein
Copy link

@lisachenko I will look into the AOP-PHP extension - I was aware of it, but I prefer a pure PHP approach (also like you said "project is not maintained for 6 monthes and doesn't working for PHP>=5.5"). In the meanwhile, it would be nice if some solution could be found for Go! to support this kind of applications.

I will continue to follow your project - at least for supporting core classes and/or functions - even if mixed HTML / PHP code is (for now) not supported.

Thanks.

@lisachenko
Copy link
Member Author

One more possible way to handle your cases is to utilize uopz or runkit extensions. They have possibility to redefine functions and methods on the fly. This can be also an option for my framework as an alternative weaving engine.

@lisachenko
Copy link
Member Author

I will continue to follow your project - at least for supporting core classes and/or functions - even if mixed HTML / PHP code is (for now) not supported.

Thanks :) 👍

@TheCelavi
Copy link
Contributor

runkit is stalled, it is not compatible with PHP 7 and there are no activities on it.

uopz changed its api, it is compatible wiht PHP 7, but seams hard or impossible to achieve AOP weaving with it.

I don't believe that for now it is going to be possible to utilize those extension.

@lisachenko
Copy link
Member Author

@TheCelavi It is still possible ) But requires AST transformation support from PHP-Parser with preseration of white nodes.

Idea is following: we define a pointcut for system namespace like this: execution(public DateTime->*(*)). During AOP initialization we can generate our classical proxies for system classes, but in hidden namespace or prefixed DateTime__AopProxied. Then for each loaded file in app (even outside of appDirectory and includePaths option) we search for two things:

  1. use DateTime; => replace it with use DateTime__AopProxied as DateTime;
  2. new \DateTime, \DateTime::class, etc => \DateTime__AopProxied

It's complex, but doable.

@TheCelavi
Copy link
Contributor

TheCelavi commented Aug 16, 2017

Great minds think alike :D

I was thinking similar idea:

  1. Provide a System namespace and to wrap all PHP core classes, example:
    namespace System;

   class ReflectionClass extends \ReflectionClass {  ...  }
  1. Export it as packagist library
  2. Write a composer plugin that will
    1. On composer install/update start -> restore all directories with source autoladin lib (see step 2)
    2. When composer is done, on post install/update (before classmap is generated) -> backup all original classes to some directory and replace them with "compiled ones" where PHP system classes are replaced with ones from System namespace

so, instanceof will work as expected, and we can weave them :) that was my idea...

Concept is based on: "If I can not hook into PHP system classes and do the monkey patching, I can wrap it with something on which I can do monkey patching" - right?

@TheCelavi
Copy link
Contributor

TheCelavi commented Aug 16, 2017

Yeah - because problem are vendor classes which uses system classes that I can not weave. What is under my control (like src dir) -> I can do replacement by myself safely and weave as I want to...

@lisachenko
Copy link
Member Author

Concept is based on: "If I can not hook into PHP system classes and do the monkey patching, I can wrap it with something on which I can do monkey patching" - right?

Yes, but there are so many limitations in this solution. So it can quickly become unreliable. I even had a thought about dropping support of interception for object initialization and function execution. They are very fragile.

@TheCelavi
Copy link
Contributor

For now - I think that we should focus on current issues, documentation, stabilizing library, tune it up, polishing, optimizing and so on...

Hopefully that will attract more users & contributors which would bring more resources and clever ideas and approaches... I dont think that currently only two of us could deal with that at the moment.

@lisachenko lisachenko changed the title [Feature] Interception of methods in the internal PHP classes [Feature][Long-Term] Interception of methods in the internal PHP classes Aug 16, 2017
@rahmatrh199
Copy link

Hi Dear,
This is my Aspect code
namespace Demo\Aspect;

use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use mysqli;

class Mysql implements Aspect {

/**

public function beforeQueryFunctions(FunctionInvocation $invocation) {
echo "mysql method executed";
echo "<script>console.log('Debug Objects:hello' );</script>";

  $method = $invocation->getFunction();
  $args = $invocation->getArguments();
  $query = $args[0];
  if ($this->logger->isTraceEnabled()) {
      $this->logger->trace($method->getName().'('.$query.')');
  }

  $startTime = VMerlinHelper::timestampValue();
  try {
      $result = $invocation->proceed();
      return $result;
  } catch(Exception $e) {
      if ($this->logger->isTraceEnabled()) {
          $this->logger->trace($method->getName().'('.$query.') '.get_class($e).': '.$e->getMessage());
      }
      throw $e;
  }

}
}

And i am getting this error

[Mon Feb 03 16:00:09.451595 2020] [php7:error] [pid 12915] [client ::1:30563] PHP Fatal error: Uncaught Dissect\Parser\Exception\UnexpectedTokenException: Unexpected public at line 1.\n\nExpected one of namePart. in /var/www/html/Agent/framework/vendor/jakubledl/dissect/src/Dissect/Parser/LALR1/Parser.php:60\nStack trace:\n#0 /var/www/html/Agent/framework/src/Core/AbstractAspectLoaderExtension.php(121): Dissect\Parser\LALR1\Parser->parse()\n#1 /var/www/html/Agent/framework/src/Core/AbstractAspectLoaderExtension.php(71): Go\Core\AbstractAspectLoaderExtension->parseTokenStream()\n#2 /var/www/html/Agent/framework/src/Core/GeneralAspectLoaderExtension.php(77): Go\Core\AbstractAspectLoaderExtension->parsePointcut()\n#3 /var/www/html/Agent/framework/src/Core/AspectLoader.php(181): Go\Core\GeneralAspectLoaderExtension->load()\n#4 /var/www/html/Agent/framework/src/Core/AspectLoader.php(100): Go\Core\AspectLoader->loadFrom()\n#5 /var/www/html/Agent/framework/src/Core/CachedAspectLoader.php(74): Go\Core\AspectLoader->load()\n#6 /var/www/html/Agent/framework/src/Core/AspectLoader.php(121): Go\Core\CachedAspectLoader->load( in /var/www/html/Agent/framework/src/Core/AbstractAspectLoaderExtension.php on line 139, referer: http://localhost/Agent/framework/demos/?showcase=MysqlTest

Tell me Where is the isssue in code

@rahmatrh199
Copy link

Tell me that ,Is this Aop framework will work for predefine classes ,
Because i need to trace http calls and db call in my application

@lisachenko
Copy link
Member Author

@rahmatrh199 This feature is not supported, all internal (pre-defined) classes could not be processed by framework as there is no source code to transform. You could use a wrapper (eg. define a class that extends original) and then it will be possible to intercept methods.

Support for internal classes could be implemented in version 4 of framework via lisachenko/z-engine. But I couldn't promise that version will be available soon.

@rahmatrh199
Copy link

rahmatrh199 commented Feb 4, 2020

@lisachenko Thank you so much for the quick response. I hope version 4 will be available soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants