modify a method/function at runtime
I've been looking at the php reflection methods, what i want to do is inject some code after the method is opened and before any retu开发者_运维技巧rn value, for example i want to change:
function foo($bar)
{
$foo = $bar ;
return $foo ;
}
And inject some code into it like:
function foo($bar)
{
//some code here
$foo = $bar ;
//some code here
return $foo ;
}
possible?
Well, one way, would be to make all the method calls "virtual":
class Foo {
protected $overrides = array();
public function __call($func, $args) {
$func = strtolower($func);
if (isset($this->overrides[$func])) {
// We have a override for this method, call it instead!
array_unshift($args, $this); //Add the object to the argument list as the first
return call_user_func_array($this->overrides[$func], $args);
} elseif (is_callable(array($this, '_' . $func))) {
// Call an "internal" version
return call_user_func_array(array($this, '_' . $func), $args);
} else {
throw new BadMethodCallException('Method '.$func.' Does Not Exist');
}
}
public function addOverride($name, $callback) {
$this->overrides[strtolower($name)] = $callback;
}
public function _doSomething($foo) {
echo "Foo: ". $foo;
}
}
$foo = new Foo();
$foo->doSomething('test'); // Foo: test
PHP 5.2:
$f = create_function('$obj, $str', 'echo "Bar: " . $obj->_doSomething($str) . " Baz";');
PHP 5.3:
$f = function($obj, $str) {
echo "Bar: " . $obj->_doSomething($str) . " Baz";
}
All PHP:
$foo->addOverride('doSomething', $f);
$foo->doSomething('test'); // Bar: Foo: test Baz
It passes the instance of the object as the first method to the "override". Note: This "overriden" method will not have access to any protected members of the class. So use getters (__get
, __set
). It WILL have access to protected methods, since the actual call is coming from __call()
...
Note: You'll need to modify all your default methods to be prefixed with an '_' for this to work... (or you can chose another prefix option, or you can just scope them all protected)...
Look into anonymous functions. If you can run PHP 5.3 that might be more along the lines of what you're trying to do.
This isn't possible, at least not in the way you are after. As the comment suggested, reflection is for getting information about classes/functions, not modifying them.
There is a PHP extension called Runkit which I believe provides this type of functionality - http://www.php.net/manual/en/book.runkit.php, however this isn't going to be installed by default on the vast majority of hosts out there.
There may be a different way of doing this though. Perhaps if you could give some more info on what you're trying to do, and why you can't modify the function in question, we might be able to provide some pointers. E.g. is the function you want to modify a core PHP function, or code in a third party library which you don't want to edit?
You could add a special autoloader before the original autoloader, read the file that your original autoload would load, change the content, save that file somewhere else (some tmp dir for example) and include that modified file instead of the original one.
Just an idea:
function foo($bar, $preprocess, $postprocess){
//IF CONDITION($preprocess)
include('/mylogic/'.$preprocess.".php"); //do some preprocessing here?
$foo = $bar ;
//IF CONDITION($postprocess)
include('/mylogic/'.$postprocess.".php"); //process $foo here?
//FINALLY return
return $foo;
}
Maybe I'm missing something, but do you really want to "inject" code as you say? What are you trying to achieve? If you simply want to execute one block of code when A happens, and another when B happens, then all you need is simple programming logic, like an if() statement.
Are you really trying to alter a function at runtime? Or is this just a logic problem?
Be more specific about what you need to do.
I wanted to do the exact same thing, so I created a generic instrumentation class that would replace the class I was trying to instrument and leveraging @ircmaxell technique it would call through to the class to be instrumented.
<?php
class GenericClassInstrumentor
{
private $instrumentedClass;
private $className;
public function __construct($instrumentedClass)
{
$this->instrumentedClass = $instrumentedClass;
$this->className = get_class($instrumentedClass);
}
public function __call($name, $arguments)
{
$start = microtime(true);
$result = call_user_func_array(array($this->instrumentedClass, $name), $arguments);
$end = microtime(true);
$duration = ($end - $start) * 1000;
// optionally log something here
return $result;
}
}
Say you had an instance of a class like $myClass
, that had function foo
on it, then you would do something like this:
$myClass = new ClassWithFoo();
if ($instrumentationOn) {
$myClass = new GenericClassInstrumentor($myClass);
}
Calling $myClass->foo($bar)
would work the same (with the same caveats as @ircmaxell answer) if $instrumentationOn
is true
or false
. If it is true
it would just do the extra timing stuff.
精彩评论