Абстрактные константы в PHP. Заставьте дочерний класс определить константу

Я заметил, что в PHP не может быть абстрактных констант.

Есть ли способ заставить дочерний класс определить константу (которую мне нужно использовать в одном из внутренних методов абстрактного класса)?


person Alex    schedule 29.04.2012    source источник
comment
Константа с полной областью действия после установки доступна в каждом классе, методе функции или чем-то еще. это не имеет смысла, пожалуйста, предоставьте код, чтобы объяснить немного больше.   -  person Lawrence Cherone    schedule 29.04.2012
comment
Либо определите константу в абстрактном классе (таким образом, у каждого дочернего класса есть константа, даже если он не определяет свою собственную), либо используйте абстрактную функцию (которая заставляет каждый дочерний класс определять свою собственную). Изменить: короткий ответ на ваш вопрос - нет.   -  person halfer    schedule 29.04.2012
comment
Если вам нужно установить значение во время выполнения, то оно по определению является переменной.   -  person Francisco Luz    schedule 24.12.2018


Ответы (6)


constant есть constant; насколько я знаю, в PHP нет констант abstract или private, но вы можете обойти это:

Образец абстрактного класса

abstract class Hello {
    const CONSTANT_1 = 'abstract'; // Make Abstract
    const CONSTANT_2 = 'abstract'; // Make Abstract
    const CONSTANT_3 = 'Hello World'; // Normal Constant
    function __construct() {
        Enforcer::__add(__CLASS__, get_called_class());
    }
}

Все будет работать нормально

class Foo extends Hello {
    const CONSTANT_1 = 'HELLO_A';
    const CONSTANT_2 = 'HELLO_B';
}
new Foo();

Bar вернет ошибку

class Bar extends Hello {
    const CONSTANT_1 = 'BAR_A';
}
new Bar();

Songo вернет ошибку

class Songo extends Hello {

}
new Songo();

Класс Enforcer

class Enforcer {
    public static function __add($class, $c) {
        $reflection = new ReflectionClass($class);
        $constantsForced = $reflection->getConstants();
        foreach ($constantsForced as $constant => $value) {
            if (constant("$c::$constant") == "abstract") {
                throw new Exception("Undefined $constant in " . (string) $c);
            }
        }
    }
}
person Baba    schedule 29.04.2012
comment
Это не константа, это функция. - person Zombaya; 29.04.2012
comment
@Zombaya, спасибо за наблюдение. - person Baba; 29.04.2012
comment
@Baba хорошая работа :) Но означает ли это, что для каждой константы будет оператор if? - person Songo; 29.04.2012
comment
@Баба хахахаха :D ты поместил меня в код!!! :D хорошо, но теперь нет возможности отличить обычные константы от абстрактных :D - person Songo; 29.04.2012
comment
Есть ... все определения constant в классе abstract обрабатываются как abstract, но все определения constant в дочернем просто не принудительно ... На этот раз я понял @Songo - person Baba; 29.04.2012
comment
@Baba Меня здесь загоняют в угол :)))) ОК, мой последний шанс Как бы вы добавили нормальные константы в абстрактный класс? ;) обойдите это, и я объявлю о своем поражении: P - person Songo; 29.04.2012
comment
@Songo .. вы можете видеть обновленный код .. он уже работает для нормального и абстрактного .... я меняю ключевые слова, чтобы вы могли понять - person Baba; 30.04.2012
comment
@Баба Ага! Теперь я понял :D Хорошая работа ;) На самом деле нужно проголосовать дважды :D - person Songo; 30.04.2012
comment
лол... жаль, что ты не можешь этого сделать.... как я уже сказал... я так просто не сдаюсь... :D - person Baba; 30.04.2012
comment
Хотя это работает, я чувствую, что это решение довольно громоздкое, ненадежное и создает ненужную сложность. Если абстрактный класс зависит от информации времени выполнения, @Alex должен либо использовать шаблон Метод шаблона с Константными методами, либо проверять методы обработки на наличие значения константы по умолчанию. Кроме того, жестко запрограммированную зависимость от Enforcer в ctor легко упустить из виду. Подкласс может переопределить ctor, и тогда вся эта грязная возня с Reflection API больше не будет работать. - person Gordon; 27.10.2012
comment
В первой строке написано A constant is a constant .. there is no abstract or private, это просто пример обходного пути.. не идеальный, не для использования в производстве... только в образовательных целях.... Если у вас есть лучшее решение, просто добавьте его... - person Baba; 27.10.2012
comment
Мой комментарий включает два предложения по лучшему решению. Кроме того, если вы знаете, что ваше решение является лишь проверкой концепции и не предназначено для использования в производстве, в ответе должно быть указано именно это. Люди, приходящие в SO, обычно обращаются за помощью по реальным проблемам, с которыми они сталкиваются в продакшене. Но они не обязательно знают, что что-то является плохой практикой, взломом или просто для образовательных целей. Если только так не сказали. Вот почему я указываю на это. - person Gordon; 27.10.2012
comment
У меня есть небольшая проблема с if (constant($c::$constant) == abstract). Я получаю исключение, если значение константы = 0. Целое число 0!. сравнение === устранило проблему. - person printminion; 22.10.2013
comment
Я использую трейт в качестве шаблона цепочки ответственности, который требует, чтобы объект зарегистрировал константу CHAIN_HANDLER для передачи в метод регистрации цепочки. Этот ответ был очень полезен для этого, +1. - person mopsyd; 26.11.2014
comment
+1 @Gordon, использование шаблона проектирования метода шаблона для этой цели кажется самым чистым вариантом. Понятно, что подтипы должны его реализовать и я всегда могу использовать его в базовом коде. sourcemaking.com/design_patterns/template_method/php - person Onema; 10.06.2015
comment
@WebSmithery предоставил гораздо лучший ответ и должен быть принятым ответом - person MC57; 22.08.2018

Это может быть что-то вроде «хака», но делает работу с очень небольшими усилиями, но с другим сообщением об ошибке, если константа не объявлена ​​в дочернем классе.

Объявление самореферентной константы синтаксически корректно и анализируется без проблем, выдавая ошибку только в том случае, если это объявление действительно выполняется во время выполнения, поэтому самореферентное объявление в абстрактном классе должно быть переопределено в дочернем классе. class иначе будет фатальная ошибка: Cannot declare self-referencing constant.

В этом примере абстрактный родительский класс Foo заставляет всех своих потомков объявлять переменную NAME. Этот код работает нормально, выводя Donald. Однако если дочерний класс Fooling не объявил переменную, возникнет фатальная ошибка.

<?php

abstract class Foo {

    // Self-referential 'abstract' declaration
    const NAME = self::NAME;

}

class Fooling extends Foo {

    // Overrides definition from parent class
    // Without this declaration, an error will be triggered
    const NAME = 'Donald';

}

$fooling = new Fooling();

echo $fooling::NAME;
person WebSmithery    schedule 31.03.2017
comment
Спасибо @WebSmithery - person Dos; 26.06.2017
comment
На мой взгляд, это самое элегантное решение - спасибо! - person Mike Harrison; 03.10.2018
comment
Просто к вашему сведению, я только что попробовал это в php 7.2, и это не работает. Исключение: фатальная ошибка: необработанная ошибка: невозможно объявить самореферентную константу 'self :: RAW_DATA_CACHE_KEY' - person Chris Flannagan; 07.11.2018
comment
Я только что протестировал PHP 7.2.19, и он работает как шарм. Обратите внимание, что вы не можете вызывать self::NAME внутри класса, но $this::NAME. - person StR; 05.08.2019
comment
@ChrisFlannagan попробуй static::NAME - person Lucas Bustamante; 12.09.2019
comment
Как ни странно, это не работает, если значение константы расширенного класса исходит из define, хотя обычно константу класса можно указать через define (PHP 7.3.11). - person Jake; 06.08.2020
comment
@LucasBustamante это приводит к фатальной ошибке PHP: static:: не допускается в константах времени компиляции. - person dirdi; 06.12.2020
comment
PhpStorm 2019.3.4 неправильно помечает это как ошибку. Невозможно объявить константу, ссылающуюся на себя, однако это ошибка, это допустимый синтаксис. Проголосуйте за сообщение об ошибке: youtrack.jetbrains.com/issue/WI-58073 - person Lucas Bustamante; 18.01.2021

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

Однако... вы можете проверить, определена ли константа в конструкторе родительского класса. Если это не так, сгенерируйте исключение.

abstract class A
{
    public function __construct()
    {
        if (!defined('static::BLAH'))
        {
            throw new Exception('Constant BLAH is not defined on subclass ' . get_class($this));
        }
    }
}

class B extends A
{
    const BLAH = 'here';
}

$b = new B();

Это лучший способ сделать это из вашего первоначального описания.

person Jamie Rumbelow    schedule 29.04.2012
comment
Значение константы может быть переопределено в дочернем классе. Константы не защищены от такого переопределения. - person Brilliand; 12.02.2015
comment
@Brilliand да, тогда это не константа. - person Alfonso Fernandez-Ocampo; 13.06.2020
comment
@AlfonsoFernandez-Ocampo Если это в другом контексте (например, в дочернем классе), то это фактически другая константа, а не изменение первой константы. Наличие константы означает, что ничто не может быть определено в другом месте, что скрывает константу, было бы довольно экстремально. - person Brilliand; 14.06.2020

Нет, но вы можете попробовать другие способы, такие как абстрактные методы:

abstract class Fruit
{
    abstract function getName();
    abstract function getColor();

    public function printInfo()
    {
        echo "The {$this->getName()} is {$this->getColor()}";
    }
}

class Apple extends Fruit
{
    function getName() { return 'apple'; }
    function getColor() { return 'red'; }

    //other apple methods
}

class Banana extends Fruit
{
    function getName() { return 'banana'; }
    function getColor() { return 'yellow'; }

    //other banana methods
}  

или статические члены:

abstract class Fruit
{
    protected static $name;
    protected static $color;

    public function printInfo()
    {
        echo "The {static::$name} is {static::$color}";
    }
}

class Apple extends Fruit
{
    protected static $name = 'apple';
    protected static $color = 'red';

    //other apple methods
}

class Banana extends Fruit
{
    protected static $name = 'banana';
    protected static $color = 'yellow';

    //other banana methods
} 

Источник

person Songo    schedule 29.04.2012
comment
Если вы копируете код из другого места в Интернете, убедитесь, что вы также цитируете источник. Приведенный выше код был взят с сайта sitepoint.com/forums/showthread.php? 629565-Абстрактные-константы - person Gordon; 27.10.2012
comment
Это лучше, чем принятый ответ. Если абстрактный класс зависит от дочернего класса, определите абстрактный метод для объявления этой зависимости и используйте этот метод для получения значения из реализующего класса. - person Kwebble; 09.06.2016

Протестировано в php 7.2, но начиная с 5.3 вы можете использовать позднюю статическую привязку для архивирования этого поведения. Это вызовет Fatal Error, достигающую того же, что и Exception, потому что в большинстве случаев вы не хотите обрабатывать Fatal Errors во время выполнения. Если вы хотите, вы можете легко реализовать собственный обработчик ошибок.

Итак, для меня работает следующее:

<?php

abstract class Foo {

    public function __construct() {
        echo static::BAR;
    }

}


class Bar extends Foo {
    const BAR = "foo bar";
}

$bar = new Bar();    //foo bar

Если вы удалите const, вы получите:

Fatal error: Uncaught Error: Undefined class constant 'BAR' in ...
person Code Spirit    schedule 20.02.2019
comment
К сожалению, вы не получаете ошибку времени компиляции — вы получаете ошибку только при выполнении echo static::BAR;. IDE или статический анализатор не сообщат автору класса Bar, что он должен определить константу. - person bdsl; 04.08.2020

Интерфейсы PHP поддерживают константы. Это не так идеально, потому что вам нужно помнить о реализации интерфейса в каждом дочернем классе, поэтому это частично противоречит цели.

person Adam Berry    schedule 14.04.2020