【问题标题】:Capturing the calling class up the hierarchy in PHP在 PHP 的层次结构中捕获调用类
【发布时间】:2012-01-15 13:55:43
【问题描述】:

序言

我追求的是;如果某个方法调用了get_typed_ancestor() 方法,则执行get_typed_ancestor() 中所需操作所需的类名是调用方法所在的类的名称​​定义

传递$this 以提取类名失败,因为它将解析为具体类。如果调用方法是在比实例所在的具体类更高层次结构的抽象类中定义的,我们会得到不正确的类名。

查找instanceof static 失败的原因与上述相同。

如下所述,捕获定义方法的类名的目的是为了让get_typed_ancestor() 可以找到从定义调用方法的类派生的任何类的实例,而不仅仅是另一个实例启动调用堆栈的具体类(因此$thisstatic 不令人满意

到目前为止,将__CLASS__ 传递给get_typed_ancestor() 似乎是迄今为止唯一的解决方案,因为__CLASS__ 将正确解析为定义调用方法的类名,而不是实例的类名调用调用方法。


注意

我在这个问题的末尾包含了一个示例,显示了有效的__CLASS__ 参数方法和失败的static 方法。如果你想试一试,不妨以此为起点。


问题

我已经看到了几个“解决方案”,它们利用debug_backtrace() 来捕获给定方法或函数的调用类;然而,就我而言,这些(我的引号可能暗示)并不完全是解决方案,因为以这种方式使用debug_backtrace() 是一种黑客行为。

吐槽一下,如果这个破解是唯一的答案,那么我会破解。

无论如何;我正在研究一组充当自下而上可遍历树中的节点的类。这是一个类层次结构,为简洁起见进行了简化:

abstract class AbstractNode{}

abstract class AbstractComplexNode extends AbstractNode{}

class SimpleNode extends AbstractNode{}

class ComplexNodeOne extends AbstractComplexNode{}

class ComplexNodeTwo extends AbstractComplexNode{}

节点可以有任何具体节点(null)作为父节点。看着AbstractNode

abstract class AbstractNode{

    protected $_parent;

    public function get_typed_ancestor(){
        // here's where I'm working
    }

    public function get_parent(){
        return $this->_parent;
    }

}

get_typed_ancestor() 方法就是我所在的位置。

从扩展类中的其他方法中,调用get_typed_ancestor() 以查找该方法所属的类类型中最接近的_parent。用一个例子可以更好地说明这一点;鉴于之前的AbstractNode 定义:

abstract class AbstractComplexNode extends AbstractNode{

    public function get_something(){
        if(something_exists()){
            return $something;
        }
        $node = $this->get_typed_ancestor();
        if(null !== $node){
            return $node->get_something();
        }
    }

}

方法get_typed_ancestor(),当从AbstractComplexNode::get_something() 的上下文中调用时,将寻找AbstractComplexNode 类型(或扩展类型)的对象——在这种层次结构的情况下,可能的具体类是ComplexNodeOneComplexNodeTwo

由于无法实例化AbstractComplexNode,因此ComplexNodeOne 等具体实例将调用get_something()

我需要在这里强调一点;在前面的例子中搜索必须AbstractComplexNode,以便找到ComplexNodeOneComplexNodeTwo。稍后将解释,搜索和instanceof static 将失败,因为它可能会跳过兄弟类和/或其子类的实例。

问题是,因为在某些情况下调用类是抽象的,并且调用方法是由(因此从一个实例调用)a类如ComplexNodeOne,搜索instanceofstatic 的父级不起作用,因为static 后期绑定到具体的ComplexNodeOne

现在,我有一个解决方案,但我不喜欢它:

abstract class AbstractNode{

    public function get_typed_ancestor($class){
        $node = $this;
        while(null !== $node->_parent){
            if($node->_parent instanceof $class){
                return $node->_parent;
            }
            $node = $node->_parent;
        }
        return null;
    }

}

abstract class AbstractComplexNode extends AbstractNode{

    public function get_something(){
        if(something_exists()){
            return $something;
        }
        $node = $this->get_typed_ancestor(__CLASS__);
        if(null !== $node){
            return $node->get_something();
        }
    }

}

这似乎可行,因为__CLASS__ 解析为定义的类名。不幸的是,我尝试使用__CLASS__ 作为get_typed_ancestor() 的默认参数,但没有成功(虽然这是意料之中的

我正在考虑将 $class 参数保留为可选参数,但如果有可能“隐式”将此数据传递给方法(在没有可选参数的情况下 ) 那太好了。


解决方案/失败

  • 将调用方法中的__CLASS__ 作为参数传递给get_typed_ancestor()
    有效,但并不理想,因为我希望 get_typed_ancestor() 在没有明确通知的情况下解析调用类。

  • 在搜索循环中,检查if($node->_parent instanceof static)
    当调用类继承调用方法时不起作用。它解析为调用方法的具体类,而不是定义的类。这种失败当然也适用于selfparent

  • 使用debug_backtrace() 捕获$trace[1]['class'] 并将其用于检查。
    有效,但并不理想,因为它是一种 hack。

讨论分层数据结构和支持类层次结构是很棘手的,而不会让你的听众感到困惑。


示例

abstract class AbstractNode
{
    protected $_id;

    protected $_parent;

    public function __construct($id, self $parent = null)
    {
        $this->_id = $id;
        if(null !== $parent)
        {
            $this->set_parent($parent);
        }
    }

    protected function get_typed_ancestor_by_class($class)
    {
        $node = $this;
        while(null !== $node->_parent)
        {
            if($node->_parent instanceof $class)
            {
                return $node->_parent;
            }
            $node = $node->_parent;
        }
        return null;
    }

    public function get_typed_ancestor_with_static()
    {
        $node = $this;
        while(null !== $node->_parent)
        {
            if($node->_parent instanceof static)
            {
                return $node->_parent;
            }
            $node = $node->_parent;
        }
        return null;
    }

    public function set_parent(self $parent)
    {
        $this->_parent = $parent;
    }

}

class SimpleNode extends AbstractNode
{

}

abstract class AbstractComplexNode extends AbstractNode
{

    public function test_method_class()
    {
        var_dump($this->get_typed_ancestor_by_class(__CLASS__));
    }

    public function test_method_static()
    {
        var_dump($this->get_typed_ancestor_with_static());
    }

}

class ComplexNodeOne extends AbstractComplexNode
{

}

class ComplexNodeTwo extends AbstractComplexNode
{

}

$node_1 = new SimpleNode(1);
$node_2 = new ComplexNodeTwo(2, $node_1);
$node_3 = new SimpleNode(3, $node_2);
$node_4 = new ComplexNodeOne(4, $node_3);
$node_5 = new SimpleNode(5, $node_4);
$node_6 = new ComplexNodeTwo(6, $node_5);

// this call incorrectly finds ComplexNodeTwo ($node_2), skipping
// the instance of ComplexNodeOne ($node_4)
$node_6->test_method_static();
//    object(ComplexNodeTwo)#2 (2) {
//      ["_id":protected]=>
//      int(2)
//      ["_parent":protected]=>
//      object(SimpleNode)#1 (2) {
//        ["_id":protected]=>
//        int(1)
//        ["_parent":protected]=>
//        NULL
//      }
//    }

// this call correctly finds ComplexNodeOne ($node_4) since it's
// looking for an instance of AbstractComplexNode, resolved from
// the passed __CLASS__
$node_6->test_method_class();
//    object(ComplexNodeOne)#4 (2) {
//      ["_id":protected]=>
//      int(4)
//      ["_parent":protected]=>
//      object(SimpleNode)#3 (2) {
//        ["_id":protected]=>
//        int(3)
//        ["_parent":protected]=>
//        object(ComplexNodeTwo)#2 (2) {
//          ["_id":protected]=>
//          int(2)
//          ["_parent":protected]=>
//          object(SimpleNode)#1 (2) {
//            ["_id":protected]=>
//            int(1)
//            ["_parent":protected]=>
//            NULL
//          }
//        }
//      }
//    }

【问题讨论】:

    标签: php oop class inheritance


    【解决方案1】:

    为了解决“捕获给定方法或函数的调用类”的问题,只需在构造函数中传递创建实例的对象即可。

    <?php
    
    class A {
      public function caller() {
        $b = new B ($this);
        $b->bar();
      }
    }
    
    class B {
      $whoClass = '';
      public function __construct($who)
      {
        $this->whoClass = get_class($who);
      }
      public function bar($who) {
        echo get_class($this->whoClass);
      }
    }
    

    【讨论】:

    • +1 这是正确的解决方案,而不是 OP 正在考虑的反射/回溯疯狂。像这样使用这些重型大炮是程序员地狱的一种方式。
    • 感谢@macjohn -- 传递上下文(通过$this)并不能完全解决问题。与我的示例一样,如果传递了ComplexNodeOne 的实例,则检索到的类就是这样。我所追求的是,​​如果我调用由ComplexNodeOneAbstractComplexNode 中定义)继承的方法,则检索到的类应该是AbstractComplexNode,不是$this 实例的类。这将允许搜索找到从AbstractComplexNode 派生的任何实例,而不仅仅是具体上下文对象的另一个实例。
    猜你喜欢
    • 1970-01-01
    • 2017-07-11
    • 2010-11-14
    • 1970-01-01
    • 2011-05-26
    • 2012-07-18
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    相关资源
    最近更新 更多