开发者

Manipulate PHP-instanceof-operator for wrapper-class

开发者 https://www.devze.com 2023-02-09 11:09 出处:网络
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

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开发者_JAVA百科... :-)

Thanks for your answers, Ben


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).


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.


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


Use an interface instead of the concrete class. Apply the interface to Wrapper and Concrete Class.

See http://de3.php.net/manual/en/language.oop5.interfaces.php


Have a look at decorator pattern. If your wrapper/wrapped classes implement the same interface, you can do everything elegantly (and use instanceof interface throughout the code).

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?

You cannot manipulate instanceof operator. Since you were interested how instanceof operator is implemented, here is a PHP representation of original C code:

class php_class {
    public $interfaces = array(); // array of php_class objects (php classes can implement more than one interface)
    public $parent = null;  // php_class object (php classes can only extend one class)
}

function instanceof_operator($implementation, $abstraction) {
    // forward recursion (iterates recursively through interfaces until a match is found)
    for($i=0; $i<count($implementation->interfaces); $i++) {
        if(instanceof_operator($implementation->interfaces[$i], $abstraction)) {
            return true;
        }
    }
    // backward recursion (iterates recursively through parents until a match is found)
    while($implementation!=null) {
        if($implementation == $abstraction) {
            return true;
        }
        $implementation = $implementation->parent;
    }
    // no match was found
    return false;
}

Whenever you declare a class to implement/extend an interface/class, imagine an entry is deposited on $interfaces or $parent fields that remains immutable until script terminates.

0

精彩评论

暂无评论...
验证码 换一张
取 消