Захват вызывающего класса вверх по иерархии в PHP - PullRequest
6 голосов
/ 08 декабря 2011

Преамбула

То, что я после того, как; если метод вызывает метод get_typed_ancestor(), имя класса, необходимое для выполнения операций, требуемых в get_typed_ancestor(), является именем класса, в котором вызывающий метод определен .

Передача $this для извлечения имени класса завершается неудачно, поскольку оно разрешается в конкретный класс. Если вызывающий метод определен в абстрактном классе выше иерархии, чем конкретный класс, экземпляром которого является экземпляр, мы получим неправильное имя класса.

Поиск instanceof static завершается неудачно по той же причине, что и описанная выше.

Как описано ниже, цель захвата имени класса, в котором определен метод, заключается в том, чтобы get_typed_ancestor() мог найти экземпляр любого класса, производного от класса, в котором определен вызывающий метод, а не просто другой экземпляр конкретного класса, который инициировал стек вызовов (, следовательно, $this и static неудовлетворительно )

Пока что передача __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 - в случае этой иерархии, возможные конкретные классы ComplexNodeOne и ComplexNodeTwo.

Поскольку экземпляр AbstractComplexNode не может быть создан, конкретный экземпляр, такой как ComplexNodeOne, будет вызывать get_something().

Мне нужно выделить точку здесь; поиск в этом предыдущем случае должен быть для AbstractComplexNode, чтобы найти первый экземпляр ComplexNodeOne или ComplexNodeTwo. Как будет объяснено ниже, поиск и instanceof static завершатся неудачно, так как могут пропустить экземпляры классов братьев и сестер и / или их детей.

Проблема заключается в том, что существуют ситуации, когда вызывающий класс является абстрактным, а вызывающий метод наследуется ( и, следовательно, вызывается из экземпляра ) такого класса как 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).
    Не работает, когда вызывающий класс наследует вызывающий метод. Он разрешает конкретный класс, в котором вызывается метод, а не тот, в котором он определен. Этот сбой, конечно, относится и к self и parent.

  • Используйте debug_backtrace() для захвата $trace[1]['class'] и используйте это для проверки.
    Работает, но не идеально, так как это взлом.

Сложно обсуждать иерархическую структуру данных и поддерживать иерархию классов, не чувствуя, что вы запутываете свою аудиторию.


Пример

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

1 Ответ

1 голос
/ 09 декабря 2011

Для решения проблемы " для захвата класса вызова данного метода или функции " просто передайте объект, который создает экземпляр, в конструкторе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...