Можно ли определять константы в абстрактном классе?

Ответ

Да, абстрактные классы могут содержать константы, как и обычные классы. Они полезны для определения общих для всех наследников неизменяемых значений.

Пример с разными уровнями доступа:

abstract class Animal {
    // Публичная константа, доступна отовсюду
    public const KINGDOM = 'Animalia';
    // Защищенная константа, доступна внутри класса и наследников
    protected const DEFAULT_SOUND = '...';
    // Приватная константа, доступна только внутри Animal
    private const SECRET_CODE = 123;

    abstract public function makeSound(): void;

    public function getSecret(): int {
        return self::SECRET_CODE; // Доступ внутри класса
    }
}

class Dog extends Animal {
    public function makeSound(): void {
        // Доступ к защищенной константе предка
        echo self::DEFAULT_SOUND;
        // echo self::SECRET_CODE; // Fatal error: приватная константа недоступна
    }
}

// Использование
echo Animal::KINGDOM; // 'Animalia'
echo Dog::KINGDOM;    // 'Animalia' (публичная константа наследуется)

$dog = new Dog();
echo $dog->getSecret(); // 123 (доступ через публичный метод предка)

Важно: константы класса (включая абстрактные) нельзя переопределять в дочерних классах.

Ответ 18+ 🔞

А, ну это вообще классика, ебать мои старые костыли! Сидишь такой, пишешь свой абстрактный класс, а потом думаешь: "А не засунуть ли мне сюда какую-нибудь константу, чтобы наследники не ебались и знали, как правильно жить?" И ведь можно, ёпта! Ровно так же, как и в обычном классе. Это ж просто значения, которые намертво вшиты в логику, общие для всей этой семейки.

Вот смотри, как это выглядит на практике, с разными уровнями доступа, чтобы не было потом "ой, а я думал, что эта константа приватная, а она, блядь, публичная":

abstract class Animal {
    // Публичная константа, доступна отовсюду
    public const KINGDOM = 'Animalia';
    // Защищенная константа, доступна внутри класса и наследников
    protected const DEFAULT_SOUND = '...';
    // Приватная константа, доступна только внутри Animal
    private const SECRET_CODE = 123;

    abstract public function makeSound(): void;

    public function getSecret(): int {
        return self::SECRET_CODE; // Доступ внутри класса
    }
}

class Dog extends Animal {
    public function makeSound(): void {
        // Доступ к защищенной константе предка
        echo self::DEFAULT_SOUND;
        // echo self::SECRET_CODE; // Fatal error: приватная константа недоступна
    }
}

// Использование
echo Animal::KINGDOM; // 'Animalia'
echo Dog::KINGDOM;    // 'Animalia' (публичная константа наследуется)

$dog = new Dog();
echo $dog->getSecret(); // 123 (доступ через публичный метод предка)

Видишь? Всё логично. Публичную — видно всем, как флаг на параде. Защищённую — только своим, наследникам, чтобы не светить лишнего. А приватную — вообще никому, кроме самого класса, хоть ты тресни. Но если очень хочется, можно сделать геттер, как я с getSecret(), и тогда хоть посмотреть значение, но не изменить — оно же константа, ёклмн.

И вот тут главный подвох, на котором многие обжигаются, ядрёна вошь: константы класса, даже в абстрактных, — их нельзя переопределить в дочерних классах. Вообще. Никак. Это не методы, где можно полиморфизм устроить. Это как табличка с правилами при входе в семью: "У нас царство — Animalia, и точка". Хоть ты в наследнике Dog объяви свою константу с таким же именем — не прокатит, будет конфликт. Они статически связаны с классом, где объявлены. Так что используй эту фичу с умом, для действительно общих, фундаментальных вещей. А то будет потом волнение ебать, когда окажется, что твоя "общая" константа на самом деле у каждого наследника должна быть своя.