#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()
.