Can we inject some more lines in a function by extending it with PHP?
Asked Answered
K

1

7

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;

  1. Is it a possible thing to do with PHP?
  2. Does it has a name in OOP/OOP Principles so I can read further?
  3. Does it make any sense or is it a bad idea?
Ketty answered 19/5, 2013 at 21:11 Comment(11)
I thought the whole point of using classes was to re-use them?Catchall
Not sure. A Proxy maybe?Samantha
Dependency Injection? Fowler is always a good resource when it comes to design patterns. martinfowler.com/articles/injection.html. Avoid eval.Schreiber
This is definitely doable with call_user_func, though it may be awkward to configure. Note this 'decoration' style is used in many programming paradigms and frameworks - but you should be careful of performance concerns. Writing out to a file for every function call could kill any program you write.Mudguard
@Schreiber No, he's talking about a [ Decorator Pattern | en.wikipedia.org/wiki/Decorator_pattern ]Mudguard
@NathanielFord; If anything, those logs will be kept as an array somewhere and be ported to .txt just before shutdown event.Ketty
@NathanielFord, I see. Thanks, suppose my other remarks still apply.Schreiber
@Imaqtpie The performance concern remains: what you're proposing means loading (at least) one new function/frame onto the stack, executing it and then loading the actual function you want to call, executing it, and then unloading both previous functions. This will imply a hit and in the worst case bad decorator code will kill your program.Mudguard
@NathanielFord; I don't think there will be a huge performance issue here. Just a single line be added to functions I target, which calls a very basic method under Event class. This method will just add a string into an array. What makes you worry alot about performance? Also, this is an idea yet. Nothing is developed, and I don't even know if it's possible at all. Performance should be my last concern at this situation.Ketty
@Imaqtpie As I indicated in the previous comment: you are suggesting a generic framework to decorate any given function with some other function. Performance is the main concern with this sort of thing. In a pre-compiled language the risk is less, but here you are probably going to have to implement a function lookup, which could be anywhere from O(n) = 1 to O(n) = n^2. So approach and performance is a concern; you might carefully interrogate why you're doing this.Mudguard
@NathanielFord; I appreciate your reply but I'm asking how this kind of thing could be done using PHP. I'm not asking how it would effect the performance of my system. Performance should be my own concern, and I have no idea what kind of function lookup issues you're talking about since there is no implementation of this feature yet. Please at least give one example and explain why the feature would be slow.Ketty
B
2

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.

Brushwork answered 20/5, 2013 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.