статическое свойство не инициализируется при вызове конструктора 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 устанавливает значение, отличное от нуля, при определении этого класса. Итак, я делаю проверку в конструкторе родительского класса, но это дает мне следующую ошибку:

Неустранимая ошибка: Uncaught Exception: static $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 еще не инициализировано при запуске конструктора родителя.

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


person FutureCake    schedule 24.11.2020    source источник
comment
Я предлагаю вам превратить его в абстрактный метод, автоматически заставляя детей реализовывать.   -  person El_Vanja    schedule 25.11.2020
comment
не могли бы вы привести пример того, как это будет работать в моем сценарии?   -  person FutureCake    schedule 25.11.2020


Ответы (2)


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

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');
        }
    }

    ...

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

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

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

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

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

person El_Vanja    schedule 24.11.2020
comment
отличный ответ большое спасибо. Действительно оцените различные способы, которыми можно было бы это сделать! - person FutureCake; 25.11.2020

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

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

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

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

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

person Héctor Paúl Cervera-García    schedule 24.11.2020