Преамбула
То, что я после того, как; если метод вызывает метод 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
, поиск родительского элемента, который является instanceof
static
, не работает, так как 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
// }
// }
// }
// }