framework
framework copied to clipboard
[Feature][Long-Term] Interception of methods in the internal PHP classes
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;
}
}
}
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 ?
@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
@lisachenko Great - I will give it a go with mysql functions...
Updated my previous answer to include some useful links for you, should be helpful.
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...
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.
@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...)...
Yes, PDO are core classes, so without this feature, framework can't handle them.
@lisachenko Understood - I will follow this issue hoping that a solution can be found...
@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;
}
}
}
@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 :)
@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) ?
@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
@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.
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.
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 :) :+1:
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.
@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:
use DateTime;=> replace it withuse DateTime__AopProxied as DateTime;new \DateTime,\DateTime::class, etc =>\DateTime__AopProxied
It's complex, but doable.
Great minds think alike :D
I was thinking similar idea:
- Provide a
Systemnamespace and to wrap all PHP core classes, example:
namespace System;
class ReflectionClass extends \ReflectionClass { ... }
- Export it as packagist library
- Write a composer plugin that will
- On composer install/update start -> restore all directories with source autoladin lib (see step 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
Systemnamespace
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?
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...
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.
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.
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 {
/**
- @param FunctionInvocation $invocation Invocation
- @Before("@execution(public mysqli->query(*))")
- @return mixed */
public function beforeQueryFunctions(FunctionInvocation $invocation) { echo "mysql method executed"; echo "";
$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
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
@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.
@lisachenko Thank you so much for the quick response. I hope version 4 will be available soon