为包装类操作PHP-instanceof-operator

时间:2011-02-06 19:08:22

标签: php aop decorator interceptor instanceof

我想为某些类设置一个通用的包装类来拦截和操作一些方法调用。方法 - 呼叫转发,拦截,到目前为止没问题。但是经过一段时间的思考,我发现了一个问题,我没有解决方案:我在我的应用程序中使用内置的instanceof-operator。当然这将不再起作用,因为包装器不是其中类的实例。我想继续使用运算符,而不是用其他函数替换它。

有没有办法为此问题实施解决方法?这个操作符如何工作?它是否调用了我可能在包装器中覆盖的类的核心功能?

我知道这不是一个真正“干净”的解决方案来操纵这个操作符,但我认为这对我来说是最简单的解决方案。而且正如我们所知,PHP中有很多东西并不干净......: - )

感谢您的回答,Ben

5 个答案:

答案 0 :(得分:2)

我不知道是否有可能以你想要的方式欺骗​​instanceof运算符(如果不是,则将类识别为子类)但我认为我找到了一个可能适合您需求的解决方案。如果我理解你的问题,那么你只需要在任何类中注入一些方法,只需对整个代码进行微小的更改。

我认为在这种情况下准备解决方案的最佳方法是使用特征(描述here)。使用traits,您可以将方法添加到任何类而无需直接继承,并且它可以覆盖基类中的方法。对于具有特征的覆盖方法,您当然需要一个子类,但它们可以动态创建。我对你的包装过程一无所知,但在我的解决方案中,我使用了一个特殊的类。让我们看看我的解决方案:

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';
}

正如您所看到的,在包装过程中发生了一切。

如果你有更多的包装器,那么你可以用其他方式准备新的包装类名(例如,用包装器名称进行组合)。

答案 1 :(得分:2)

可能我可以根据您的需求描述解决方案。 (免责声明:我是Go! AOP Framework的作者)从您的描述中看起来您希望动态地为您的方法添加其他逻辑而无需触及该类。如果我是对的,那么你可以看看Aspect-Oriented Paradigm为你的源代码引入拦截器的概念,更重要的是 - 你的原始类将不受影响。

要了解如何将其应用于您的代码,您还可以查看我的文章http://go.aopphp.com/blog/2014/10/19/caching-like-a-pro/,其中重点介绍了经典的面向对象模式(如decorator,proxy)的所有优点和缺点。我可以得出结论,由于PHP的基本复杂性和局限性,所有拦截器都无法以面向对象的方式被提取到单独的模块中以解决交叉问题。 AOP扩展了传统的OOP模型,因此可以将拦截器(称为 advices )提取到单独的类中(称为方面)。

AOP的一个很棒的功能是保留原始的类名,这意味着你不应该更改代码中的类型提醒,甚至不要劫持instanceof运算符。你将为你的班级增加逻辑。

答案 2 :(得分:0)

使用界面而不是具体类。将界面应用于Wrapper和Concrete Class。

请参阅http://de3.php.net/manual/en/language.oop5.interfaces.php

答案 3 :(得分:0)

看看decorator pattern。如果您的包装器/包装类实现相同的接口,您可以优雅地完成所有操作(并在整个代码中使用instanceof interface )。

  

有没有办法为此问题实施解决方法?这个操作符如何工作?它是否调用了我可能在包装器中覆盖的类的核心功能?

您无法操纵instanceof运算符。由于您对如何实现instanceof运算符感兴趣,因此这里是原始C代码的PHP表示:

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;
}

每当你声明一个类来实现/扩展接口/类时,想象一个条目存放在$ interfaces或$ parent字段上,它们仍然是不可变的,直到脚本终止。

答案 4 :(得分:0)

根本不可能。实际上,可能在将来:https://bugs.php.net/bug.php?id=71352