Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulate PHP-instanceof-operator for wrapper-class

I'd like to have a generic wrapper-class for some classes to intercept and manipulate some of the method-calls. Method-call-forwarding, intercepting, no problem so far. But after thinking a while, i found a problem for which i have no solution: I'm using the built-in instanceof-operator everywhere in my application. Of course this won't work anymore, because the wrapper isn't an instance of the class inside it. I would like to continue using the operator and not to replace it with an other function.

Is there a way to implement a workaround for this problem? How does this operator work? Does it call a core-function of the classes which i am probably able to overwrite in my wrapper?

I know that this would not be a really "clean" solution to manipulate this operator, but i think this would be the simplest solution for me. And as we know, there are many things in PHP which are not that clean... :-)

Thanks for your answers, Ben

like image 436
Ben Avatar asked Feb 06 '11 19:02

Ben


3 Answers

I don't know is it possible to trick a instanceof operator in way you want (recognize a class as subclass if it is not) but I think I found a solution that may suit your needs. If I understand correctly your problem then you simply want to inject some methods in any class with minimal changes in your whole code.

I think the best way to prepare a solution in this case is using traits (described here). With traits you can add methods to any class without direct inheritance and it can overwrite methods from base class. For overwriting method with traits you of course need a subclasses but they can be created dynamically. I don't know anything about your wrapping process but in my solution I used a special class for it. Lets look at my solution:

namespace someNameSpace;

//this is one of your class that you want to wrap - it can be declare under some other namespace if you need
class yourBaseClass { }

//your wrapper class as a trait
trait yourWrapper { }

//class for wrapping any object
class ObjectWrapperClass
{
    //method for change object class (described on http://stackoverflow.com/a/3243949/4662836)
    protected static function objectToObject($instance, $className)
    {
        return unserialize(sprintf('O:%d:"%s"%s', strlen($className), $className, strstr(strstr(serialize($instance), '"'), ':')));
    }

    //wrapping method
    //$object is a object to be wrapped
    //$wrapper is a full name of the wrapper trait
    public static function wrap($object, $wrapper)
    {
        //take some information about the object to be wrapped
        $reflection = new \ReflectionClass($object);
        $baseClass = $reflection->getShortName();
        $namespace = $reflection->getNamespaceName();

        //perpare the name of the new wrapped class
        $newClassName = "{$baseClass}Wrapped";

        //if new wrapped class has not been declared before we need to do it now
        if (!class_exists($newClassName)) {
            //prepare a code of the wrapping class that inject trait
            $newClassCode = "namespace {$namespace} { class {$newClassName} extends {$baseClass} { use {$wrapper}; } }";

            //run the prepared code
            eval($newClassCode);
        }

        //change the object class and return it
        return self::objectToObject($object, $namespace . '\\' . $newClassName);
    }

}

//lets test this solution

$originalObject = new yourBaseClass();

$wrappedObject = ObjectWrapperClass::wrap($originalObject, 'yourWrapper');

if ($wrappedObject instanceof yourBaseClass) {
    echo 'It is working';
}

As you can see everything is happens during wrapping process.

If you have more wrappers then you can prepare the new wrapped class name in other way (for example to be corelated with wrapper name).

like image 73
zajonc Avatar answered Nov 07 '22 00:11

zajonc


Probably I can describe a solution for your needs. (disclaimer: I'm author of Go! AOP Framework) From your description it looks like you want to dynamically add additional logic to your methods without touching the class. If I'm right, then you could have a look at Aspect-Oriented Paradigm that introduces a concept of interceptors for your source code, what is more important - your original classes will be untouched.

To have an idea, how this can be applied to your code, you could also have a look at my article http://go.aopphp.com/blog/2014/10/19/caching-like-a-pro/ that highlights all advantages and disadvantages of classical object-oriented patterns like decorator, proxy. I can make a conclusion, that all interceptors can not be extracted into separate modules in object-oriented way because of essential complexity and limitations of PHP for solving cross-cutting concerns. AOP extends traditional OOP model, so it will be possible to extract interceptors (called advices) into separate classes (called aspects).

Brilliant feature of AOP is that it keeps your original class names and this means that you shouldn't change typehints in your code or even hijack a instanceof operator. You will get your class with additional logic.

like image 30
lisachenko Avatar answered Nov 07 '22 01:11

lisachenko


Not possible at all. Actually, maybe in the future: https://bugs.php.net/bug.php?id=71352

like image 41
Luigi Pressello Avatar answered Nov 07 '22 02:11

Luigi Pressello