статическое свойство, не инициализируемое при вызове конструктора php

#php #inheritance #constructor #properties #order-of-execution

#php #наследование #конструктор #свойства #порядок выполнения

Вопрос:

У меня есть родительский класс, например:

 abstract class UiElement
{
    protected static ?string $template_name = null;

    public function __construct() {
        if(static::$template_name == null) {
            throw new Exception("static $template_name has not been set in child class", 1);
        }
    }
}
 

теперь у меня есть дочерний класс, например:

 class EmptyContent extends UiElement
{
    protected static ?string $template_name = 'empty-content';

    public function __construct() {
        parent::__construct();
    }
}
 

и назовите это так:

 $empty = new EmptyContent();
 

Я хочу убедиться, что дочерний класс в этом случае EmptyContent устанавливает значение, отличное от null при определении этого класса. Итак, я выполняю проверку в конструкторе родительского класса, но это выдает следующую ошибку:

Неустранимая ошибка: неперехваченное исключение: статическое $template_name не задано в дочернем классе в /Applications/MAMP/htdocs/private_projects/Foodio/App/index.php:108 Трассировка стека: #0 /Applications/MAMP/htdocs/private_projects/Foodio/App/index.php (126): UIElement->__construct() #1 /Applications/MAMP/htdocs/private_projects/Foodio/App/index.php (134): сбой -> __construct() #2 {main}, добавленный

Насколько я понимаю, это связано с тем, что свойство $template_name еще не инициализировано при запуске конструктора родительского элемента.

Как мне это сделать?
Если требуется дополнительная информация или разъяснения, дайте мне знать, чтобы я мог ее добавить!

Комментарии:

1. Я бы посоветовал вам превратить его в абстрактный метод, тем самым автоматически заставляя дочерние элементы реализовывать.

2. не были бы вы так любезны привести пример того, как это будет работать в моем сценарии?

Ответ №1:

Вы могли бы создать абстрактный метод вместо свойства:

 abstract class UiElement
{
    abstract protected static function getTemplateName(): string;
}

class EmptyContent extends UiElement
{
    protected static function getTemplateName(): string
    {
        return 'empty-content';
    }
}
 

Таким образом, вы не сможете создать дочерний класс без реализованного метода (принудительное определение). Должны ли вы создать дочерний элемент без него:

 class EmptyContent extends UiElement
{
}
 

PHP выдаст фатальную ошибку перед запуском вашего скрипта : Class EmptyContent contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (UiElement::getTemplateName) .

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

 class EmptyContent extends UiElement
{
    public function __construct()
    {
        if (strlen(self::getTemplateName()) === 0) {
            throw new Exception('Template name is not defined');
        }
    }

    ...
 

для принудительной проверки при создании экземпляра. Это должно быть добавлено к каждому дочернему элементу (утомительно и ВЛАЖНО), поскольку вы не можете обработать его на родительском уровне (это попытается вызвать его абстрактный метод и завершится неудачей до достижения времени выполнения).

Существует также другой подход, который вы могли бы использовать, сделав его обычным (неабстрактным) методом, который выдает родительский:

 abstract class UiElement
{
    protected function getTemplateName(): string
    {
        throw new Exception('Method not implemented');
    }
}
 

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

PS Не уверен, действительно ли метод должен быть статическим, если он будет использоваться только внутри, но вы знаете свои рассуждения лучше, чем я.

Комментарии:

1. отличный ответ, большое спасибо. Действительно цените различные способы, которыми можно было бы это сделать!

Ответ №2:

Как было предложено, вы можете использовать абстрактный метод, чтобы заставить реализующие классы определять его:

 abstract class UiElement
{
    abstract protected function getTemplateName(): string;
}
 

Затем реализующие классы должны определить, какую строку возвращать:

 class EmptyContent extends UiElement
{
    protected function getTemplateName(): string
    {
        return 'empty-content';
    }
}
 

Это означает, что везде, где вы хотите использовать это значение, вы должны получить его путем вызова $this->getTemplateName() .