PHP - Контекст создания объекта - Странное поведение - Это ошибка PHP?

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

Дело

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

      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?
    

Вопрос

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

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

ИЗМЕНИТЬ

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

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


person Ahmad Farouk    schedule 24.02.2012    source источник
comment
Я теряю из виду базу, родителя и ребенка, а также то, что расширяет. Не могли бы вы добавить несколько примеров кода?   -  person Phil    schedule 24.02.2012
comment
конечно, через пару минут я опубликую образец кода   -  person Ahmad Farouk    schedule 24.02.2012
comment
Мое предположение: предполагается, что BaseClass находится там, где определен конструктор (что на самом деле и есть), следовательно, согласно правилам наследования ParentClass имеет к нему доступ. Ошибка? Спорный. Редкий крайний случай? Безусловно.   -  person deceze♦    schedule 24.02.2012
comment
Насколько мне известно, его не следует создавать, поскольку конструктор ChildClass защищен.   -  person Ahmad Farouk    schedule 24.02.2012
comment
Не могли бы вы попробовать установить частный конструктор вместо защищенного? Интересно, что изменилось.   -  person Anthony    schedule 24.02.2012
comment
@Ahmad Да, но такова его оригинальная abstract спецификация, так что, возможно, именно здесь и определен метод.   -  person deceze♦    schedule 24.02.2012
comment
@Anthony .. создание экземпляра не удается, если объявлено закрытым .. но мне просто нужно объяснение, почему он не дает сбоя ни с защищенным конструктором BaseClass   -  person Ahmad Farouk    schedule 24.02.2012
comment
@deceze. Я пытаюсь понять то, что вы говорите ... звучит логично. вы можете объяснить больше?   -  person Ahmad Farouk    schedule 24.02.2012
comment
Не уверен, что еще сказать, тем более что это всего лишь мое предположение о том, что думает PHP. :) BaseClass::__construct определен protected, ChildClass::__construct наследует это без изменения, ParentClass::__construct наследует от BaseClass, поэтому ParentClass имеет доступ к ChildClass::__construct.   -  person deceze♦    schedule 24.02.2012
comment
Я понимаю ваш сценарий, но почему он так себя ведет .. другими словами. Получили ли два нерелевантных класса доступ друг к другу только потому, что они наследуют один и тот же базовый класс?   -  person Ahmad Farouk    schedule 24.02.2012
comment
Вероятно, это частный случай abstract методов, не от которых наследуется класс. BaseClass определяет метод __construct и его видимость, ChildClass просто реализует его ...   -  person deceze♦    schedule 24.02.2012
comment
ParentClass имеет публичный ctor, так что там все нормально. Классы в одной иерархии могут обращаться к защищенным методам.   -  person StasM    schedule 12.04.2012
comment
Пояснение к последнему комментарию: область видимости __construct - это BaseClass, поэтому ему доступно все, что вверх иерархии (не та же иерархия). При разрешении защищенных вызовов PHP всегда использует самую низкую доступную область видимости (т.е. там, где метод был впервые определен).   -  person StasM    schedule 12.04.2012


Ответы (4)


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

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

Все упирается в различие между конкретным и абстрактным методом.

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

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

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

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

person J. Bruni    schedule 24.02.2012
comment
Вы говорите, что нерелевантные классы могут создавать экземпляры друг друга только в том случае, если они наследуют один и тот же абстрактный базовый класс с его конструктором, объявленным абстрактным, без учета сигнатуры конструктора любого подкласса? - person Ahmad Farouk; 24.02.2012
comment
Как эти классы неактуальны, они реализуют один и тот же абстрактный класс! Кроме того, подписи конструкторов одинаковы (и они должны быть такими же, потому что они реализуют один и тот же абстрактный метод). В любом случае, я думаю, что этот ответ действительно объясняет такое поведение, так что вы определенно можете с ним согласиться. - person Snifff; 24.02.2012
comment
@Snifff: Вероятно, он имел в виду несвязанный, а не нерелевантный. Во всяком случае, ваш комментарий все еще актуален. - person J. Bruni; 25.02.2012
comment
@AhmadFarouk: Да, подпись любого подкласса должна игнорироваться - она ​​ДОЛЖНА быть такой же, как объявленная в абстрактном классе. Это может быть более заметным. Вы думаете, что видимость побеждает, тогда как кажется, что побеждает конкретность. Главное, что следует учитывать, - это разница между переопределением (перегрузкой) и реализацией. - person J. Bruni; 25.02.2012
comment
В любом случае, учитывая последние данные пользователей outis, которые исследовали исходный код PHP, я более склонен считать это ошибкой. Фактически, это решать разработчикам, поскольку у нас есть хорошее объяснение обоих вариантов поведения. Для вас одно очевидно правильное и ожидаемое, а другое - ошибка. На мой взгляд, оба варианта разумны. Для меня они могли бы изменить поведение в соответствии с вашими объяснениями, и это тоже было бы хорошо. Заключение: это вопрос выбора, больше, чем быть ошибкой или нет (не исключая возможности действительно быть ошибкой). - person J. Bruni; 25.02.2012

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

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

Members declared protected can be accessed only within the class itself and by inherited and parent classes.

Хотя член может быть переопределен в ChildClass (сохраняя спецификатор «protected»), он был первоначально объявлен в 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 недоступны в родственниках, независимо от того, являются ли они абстрактными. Это заставляет меня думать, что поведение abstract __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 (в частности, _ 17_ обработчик кода операции). Это в дополнение к проверкам доступа к методам, что может быть причиной различного поведения. Однако я не вижу специальной обработки для доступа к __destruct, так что очевидно, что это еще не все.

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

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

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

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

[...]

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

person outis    schedule 24.02.2012
comment
защищенным свойствам отказано в доступе, в то время как защищенный конструктор не работает !! .. Объект ChildClass не должен создаваться в первую очередь !! согласно спецификациям PHP. - person Ahmad Farouk; 24.02.2012
comment
@Ahmad Думайте об этом так: abstract объявления разделяют код между определением метода и реализацией. Если вы когда-либо работали с чем-то вроде заголовочных файлов C, вы знаете разницу. Функция definition определяет видимость. Свойства не могут быть объявлены abstract, поэтому сравнение не учитывается. - person deceze♦; 24.02.2012
comment
@outis .. спасибо за объяснение, но все же я не понимаю, почему PHP работает таким образом :) .. это происходит только с абстрактным BaseClass, который имеет абстрактный конструктор. это единственный случай, иначе разумное создание экземпляра завершится ошибкой. все же мне нужно знать, почему это происходит, но спасибо за ваши усилия - person Ahmad Farouk; 24.02.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, а дети могут видеть защищенные методы своих родителей). Такое же поведение применяется к конструкторам и абстрактным классам.

person meze    schedule 24.02.2012
comment
На самом деле нет, потому что согласно моему примеру, если BaseClass является конкретным, а не абстрактным, создание экземпляра объекта завершится ошибкой ... согласно вашему объяснению, оно должно быть успешным в обоих направлениях. он также не работает, если конструктор BaseClass не является абстрактным ... так что это приводит к очень специфическому случаю, когда BaseClass является абстрактным, а его конструктор также объявлен абстрактным - person Ahmad Farouk; 24.02.2012
comment
@AhmadFarouk, ты прав. Но он должен работать без абстрактных классов, но, похоже, есть известные проблемы с контекстом конструкторов. Обновил свой ответ. - person meze; 24.02.2012
comment
Насколько я понимаю, создание экземпляра должно завершиться ошибкой в ​​любом случае, поскольку ParentClass не наследует ни цента от ChildClass .. Ожидаемый сбой происходит для всех возможных случаев, кроме случая, который мы сейчас обсуждаем. - person Ahmad Farouk; 24.02.2012
comment
@AhmadFarouk Я думаю, что есть две стороны: 1) вы не хотите позволять создавать экземпляр класса с защищенным конструктором из другого класса, не являющегося прямым потомком 2) с другой стороны, вы хотите, чтобы все методы были согласованы и действовали одинаково способ. В любом случае, абстрактный метод или нет, не должен изменять видимость метода - сделайте отчет об ошибке. - person meze; 24.02.2012
comment
@meze .. вы меня сейчас поняли .. Абстрактный метод или нет, не должен изменять доступ к методу (видимость). поэтому модификатор protected Access является управляющим правилом для разрешения или запрета доступа, а не абстрактным объявлением. - person Ahmad Farouk; 24.02.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: вызов protected ChildClassComposed :: __ construct () из контекста 'ChildClassComposer' в /Users/quickshiftin/junk-php/change-private-of-another-class.php в строке 46

person quickshiftin    schedule 24.02.2012
comment
это фатальная ошибка, которая должна была произойти. почему этого не происходит с базовым классом, если он объявлен абстрактным .. Мне действительно нужно объяснение, почему PHP ведет себя иначе в отношении абстрактных базовых классов, чем с конкретными классами - person Ahmad Farouk; 24.02.2012
comment
Посмотрите, что говорят все, включая другой ответ. Это крайний случай использования защищенных конструкторов в абстрактном контексте. Убедитесь, что вы приняли один из ответов и поняли, что ваш курс движется в правильном направлении: D - person quickshiftin; 24.02.2012
comment
Что именно является ожидаемым поведением? Пожалуй, нынешнее поведение столь же ожидаемо. :) - person deceze♦; 24.02.2012
comment
@deceze Просто заставляю конструктор ChildClassComposed вызывать фатальный код, когда он вызывается изнутри ChildClassComposer, хотя я согласен; Текущее поведение ожидается, если вы знаете, что делаете, лол. Просто пытаюсь помочь OP достичь желаемого его поведения :) - person quickshiftin; 24.02.2012
comment
Я не прошу обходного пути ... Я знаю десятки обходных путей ... Мне просто нужно знать, почему с абстрактными классами все работает именно так. для моего рассудка мне тоже нужно объяснение, иначе это будет ошибка PHP. - person Ahmad Farouk; 24.02.2012
comment
@AhmadFarouk в качестве вашего вопроса на [email protected], чтобы узнать, является ли это ошибкой или нет. - person quickshiftin; 24.02.2012