I have an idea for the event system I'm developing for my custom framework.
Imagine a pseudo function like this.
class Test
{
public function hi()
{
Event::add(__FUNCTION__ . 'is about to run.');
return "hi";
}
}
Imagine you need to do the same for some more functions. (Maybe you want to log which functions ran at the runtime and want to log them in a separate file.)
Instead of doing this and adding Events into functions manually, can we do something like this?
class Test
{
public function hi()
{
return "hi";
}
}
// events.php (It's a pseudo code so may not work.)
// Imagine extend's purpose is to inject codes into target function
Event::bind('on', $className, $methodName, function() use ($className, $methodName)
{
return $className->$methodName->extend('before', Event::add(__FUNCTION__ . 'is about to run.'));
});
The idea is to inject hi()
function which is inside Test class
and injecting whatever we pass in extend
function by outside. 'before'
means injection has to be at the first line of target function.
Finally, the events and event bindings are kept completely abstracted away from the functions. I want to be able to bind custom things without altering the functions.
I have a feeling that we can do this by hacking around with eval()
or by toying with call_user_func()
. I'm not sure, though. Using eval()
sounds pretty bad already.
My question is;
Yes, you can. You can use AOP using GO! AOP framework which works on annotations.
For example you want to log every public method calling. Instead of adding to every function line like this.
namespace Acme;
class Controller
{
public function updateData($arg1, $arg2)
{
$this->logger->info("Executing method " . __METHOD__, func_get_args());
// ...
}
}
You can use one Aspect for all public methods of all classes of Acme namespace like this:
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Before;
class LoggingAspect implements Aspect
{
/** @var null|LoggerInterface */
protected $logger = null;
/** ... */
public function __construct($logger)
{
$this->logger = $logger;
}
/**
* Method that should be called before real method
*
* @param MethodInvocation $invocation Invocation
* @Before("execution(public Acme\*->*())")
*/
public function beforeMethodExecution(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$type = $invocation->getMethod()->isStatic() ? '::' : '->';
$name = $invocation->getMethod()->getName();
$method = $class . $type . $name;
$this->logger->info("Executing method " . $method, $invocation->getArguments());
}
}
It looks more complicated but it's more flexible.