PHP - Контекст создания объекта - Странное поведение - Это ошибка PHP? - PullRequest
6 голосов
/ 24 февраля 2012

Я не задаю типичный вопрос о том, почему какой-то код вышел из строя, но я спрашиваю о том, почему он работал. Он работал со мной во время кодирования, и мне нужно было, чтобы он вышел из строя.

Случай

  • базовый абстрактный класс с защищенным конструктором, объявленным абстрактным
  • родительский класс расширяет абстрактный класс с помощью открытого конструктора (Over ridding)
  • дочерний класс расширяет тот же абстрактный класс защищенным конструктором

      abstract class BaseClass {
        abstract protected function __construct();
      }
    
      class ChildClass extends BaseClass {
        protected function __construct(){
          echo 'It works';
         }
      }
    
      class ParentClass extends BaseClass {
        public function __construct() {
          new ChildClass();
        }
      }
    
      // $obj = new ChildClass(); // Will result in fatal error. Expected!
    
      $obj = new ParentClass(); // that works!!WHY?
    

Вопрос

Родительский класс создает экземпляр объекта дочернего класса, ионо работает!!как это получается?Насколько я знаю, объект не может быть создан, если его конструктор объявлен защищенным, за исключением случаев, когда он наследуется внутренне или из каких-либо подклассов.

Родительский класс не является подклассом дочернего класса, он не наследуетдесять центов от него (но оба расширяют один и тот же базовый абстрактный класс), так почему же создание экземпляра не дает сбоя?

EDIT

Этот случай происходит только с абстрактнымBaseClass, который также имеет абстрактный конструктор. Если BaseClass является конкретным, или если его защищенный конструктор не является абстрактным, создание экземпляра завершается неудачно, как и ожидалось ... Это ошибка PHP? Для здравого смысла мне нужно действительно объяснить, почему PHP ведет себя так в этом очень специфическом случае.

Заранее спасибо

Ответы [ 4 ]

3 голосов
/ 24 февраля 2012

Почему это работает?

Поскольку изнутри ParentClass вы предоставили доступ к абстрактному методу из BaseClass.Это тот же самый абстрактный метод, который вызывается из ChildClass, несмотря на то, что его реализация определяется сама по себе.

Все зависит от разницы между конкретным и абстрактным методом.

Вы можетеПодумайте так: абстрактный метод - это один метод с несколькими реализациями.С другой стороны, каждый конкретный метод является уникальным методом.Когда у него то же имя, что и у его родителя, оно переопределяет имя родителя (оно не реализует это).

Таким образом, когда объявлено abstract, оновсегда вызывается метод базового класса.

Подумайте о методе, объявленном abstract: Почему подписи разных реализаций не могут различаться?Почему дочерние классы не могут объявлять метод с меньшей видимостью?

В любом случае, вы только что нашли очень интересную функцию .Или, если мое понимание выше неверно и ваше ожидаемое поведение является действительно ожидаемым, то вы обнаружили ошибку.

3 голосов
/ 24 февраля 2012

Примечание: следующее было протестировано с PHP 5.3.8. Другие версии могут демонстрировать другое поведение.

Поскольку формальной спецификации для PHP не существует, нет способа ответить на это с точки зрения того, что должно произойти . Самое близкое, что мы можем получить - это утверждение о protected из руководства по PHP:

Члены, объявленные защищенными, могут быть доступны только внутри самого класса и унаследованных и родительских классов.

Хотя элемент может быть переопределен в ChildClass (с сохранением «защищенного» спецификатора), изначально он был объявлен в BaseClass, поэтому он остается видимым в потомках BaseClass.

В прямом противоречии с этой интерпретацией сравните поведение для защищенного свойства:

<?php
abstract class BaseClass {
    protected $_foo = 'foo';
    abstract protected function __construct();
}

class MommasBoy extends BaseClass {
    protected $_foo = 'foobar';
    protected function __construct(){
        echo __METHOD__, "\n";
    }
}

class LatchkeyKid extends BaseClass {
    public function __construct() {
        echo 'In ', __CLASS__, ":\n";
        $kid = new MommasBoy();
        echo $kid->_foo, "\n";
    }
}

$obj = new LatchkeyKid();

Выход:

In LatchkeyKid:
MommasBoy::__construct

Fatal error: Cannot access protected property MommasBoy::$_foo in - on line 18

Изменение абстрактного __construct на конкретную функцию с пустой реализацией дает желаемое поведение.

abstract class BaseClass {
   protected function __construct() {}
}

Однако немагические методы видны у родственников, независимо от того, являются ли они абстрактными (большинство магических методов должны быть открытыми).

<?php
abstract class BaseClass {
    abstract protected function abstract_protected();
    protected function concrete() {}
}

class MommasBoy extends BaseClass {
    /* accessible in relatives */
    protected function abstract_protected() {
        return __METHOD__;
    }
    protected function concrete() {
        return __METHOD__;
    }
}

class LatchkeyKid extends BaseClass {
    function abstract_protected() {}
    public function __construct() {
        echo 'In ', __CLASS__, ":\n";
        $kid = new MommasBoy();
        echo $kid->abstract_protected(), "\n", $kid->concrete(), "\n";
    }
}

$obj = new LatchkeyKid();

Выход:

In LatchkeyKid: 
MommasBoy::abstract_protected
MommasBoy::concrete

Если вы игнорируете предупреждения и объявляете магические методы (отличные от __construct, __destruct и __clone) как protected, они кажутся доступными в родственниках, как и с немагическими методами.

Защищенные __clone и __destruct недоступны для родственников, независимо от того, являются ли они абстрактными. Это приводит меня к мысли, что поведение абстрактного __construct является ошибкой.

<?php
abstract class BaseClass {
    abstract protected function __clone();
}

class MommasBoy extends BaseClass {
    protected function __clone() {
        echo __METHOD__, "\n";
    }
}

class LatchkeyKid extends BaseClass {
    public function __construct() {
        echo 'In ', __CLASS__, ": \n";
        $kid = new MommasBoy();
        $kid = clone $kid;
    }
    public function __clone() {}
}

$obj = new LatchkeyKid();

Выход:

In LatchkeyKid:

Fatal error: Call to protected MommasBoy::__clone() from context 'LatchkeyKid' in - on line 16

Доступ к __clone обеспечивается в zend_vm_def.h (в частности, обработчик кода операции ZEND_CLONE). Это в дополнение к проверке доступа к методам, что может быть связано с другим поведением. Тем не менее, я не вижу особого подхода для доступа к __destruct, так что, очевидно, есть еще кое-что.

Стас Малышев (привет, Стас!), Один из разработчиков PHP, изучил __construct, __clone и __destruct и сказал следующее:

В общем, функция, определенная в базовом классе, должна быть доступна всем [потомки] этого класса. Обоснование этого заключается в том, что если вы определяете функция (даже абстрактная) в вашем базовом классе, вы говорите, что это будет доступны для любого экземпляра (в том числе расширенного) этого класса. Так любой потомок этого класса может использовать его.

[...] Я проверил, почему ctor ведет себя по-другому, и это потому, что родительский ctor считается прототипом для дочернего ctor (с подписью и т. д.) только в том случае, если оно объявлено абстрактным или интерфейс. Итак, объявив ctor абстрактным или сделав его частью интерфейс, вы делаете его частью договора и, таким образом, доступны для всех иерархия. Если вы этого не сделаете, ctors совершенно не связаны с каждым другой (это отличается для всех других нестатических методов) и, таким образом, наличие родительского ctor ничего не говорит о дочернем ctor, поэтому parent видимость ctor не переносится. Так что для ctor это не ошибка. [Примечание: это похоже на ответ Дж. Бруни.]

Я все еще думаю, что это, скорее всего, ошибка для __clone и __destruct.

[...]

Я отправил ошибку # 61782 , чтобы отследить проблему с помощью __clone и __destruct.

2 голосов
/ 24 февраля 2012

РЕДАКТИРОВАТЬ: конструкторы действуют по-разному ... Ожидается, что он будет работать даже без абстрактных классов, но я нашел этот тест , который проверяет тот же случай, и похоже, что это техническое ограничение - материал, описанный ниже, не ' т работать с конструкторами прямо сейчас.

Нет ошибки . Вы должны понимать, что атрибуты доступа работают с контекстом объектов. Когда вы расширяете класс, ваш класс сможет видеть методы в контексте BaseClass. ChildClass и ParentClass оба в контексте BaseClass, поэтому они могут видеть все методы BaseClass. Зачем тебе это? Для полиморфизма:

  class BaseClass {
     protected function a(){}
  }

  class ChildClass extends BaseClass {
    protected function a(){
      echo 'It works';
     }
  }

  class ParentClass extends BaseClass {
    public function b(BaseClass $a) {
      $a->a();
    }
    public function a() {

    }
  }

Независимо от того, какой дочерний элемент вы передаете в метод ParentClass :: b (), вы сможете получать доступ к методам BaseClass (в том числе защищенным, поскольку ParentClass - это дочерний элемент BaseClass, и дочерние элементы могут видеть защищенные методы своих родителей). То же самое относится к конструкторам и абстрактным классам.

1 голос
/ 24 февраля 2012

Интересно, нет ли чего-то глючного с абстрактной реализацией под капотом, или есть тонкая проблема, которую мы пропускаем.Изменение BaseClass с абстрактного на конкретное приводит к фатальной ошибке, за которой вы следите (классы переименованы для моего здравого смысла)

РЕДАКТИРОВАТЬ: Я согласен с тем, что @deceze говорит в своих комментариях, что это крайний случайабстрактная реализация и потенциально ошибка.Это, по крайней мере, обходной путь, обеспечивающий ожидаемое поведение, вызывающее некую уродливую технику (притворный абстрактный базовый класс).

class BaseClass
{
    protected function __construct()
    {
        die('Psuedo Abstract function; override in sub-class!');
    }
}

class ChildClassComposed extends BaseClass
{
    protected function __construct()
    {
        echo 'It works';
    }
}


// Child of BaseClass, Composes ChildClassComposed
class ChildClassComposer extends BaseClass
{
    public function __construct()
    {
        new ChildClassComposed();
    }
}

Неустранимая ошибка PHP: вызов защищенного ChildClassComposed :: __ construct () из контекста 'ChildClassComposer' в / Users / quickshiftin / junk-php / change-private-of-another-class.PHP на линии 46

...