Похоже, что строгие ошибки не возникают, если классы находятся в одном файле, например:
abstract class Food{}
class Meat extends Food{}
abstract class Animal{
function feed(Food $food){ }
}
class Cat extends Animal{
function feed(Meat $meat){
parent::feed($meat);
}
}
Но если вы поместите определение класса в отдельные файлы и включите их следующим образом:
abstract class Food{}
class Meat extends Food{}
require 'Animal.php';
require 'Cat.php';
Строгое стандартное сообщение об ошибке выдается:
Строгие стандарты: декларация
Cat::feed()
должен быть совместим сAnimal::feed(Food $food)
в c: \ path \ to \ Cat.php на линии …
Если все в одном файле, это нормально:
class Dog extends Animal{
function feed($qty = 1){
for($i = 0; $i < $qty; $i++){
$Meat = new Meat();
parent::feed($Meat);
}
}
}
Это предполагаемое поведение?
Так как Meat
это Food
не должно быть жалобы в первую очередь, верно?
Таким образом, решение является простым и ясным: поместите все в один файл, и строгие стандарты будут удовлетворены;)
Любые советы приветствуются
Это предполагаемое поведение?
К сожалению, да. Сложности, которые вступают в игру с объявлениями классов, делают так, что строгие правила не всегда применяются, когда все они встречаются в одном и том же сценарии; один класс на файл не демонстрирует эту проблему.
Поскольку мясо — это еда, в первую очередь не должно быть жалоб, верно?
Неправильно по двум причинам:
Мясо является меньшим типом, чем еда, и поэтому, допуская только меньший тип в вашем потомственном классе, вы нарушаете LSP; вы не можете заменить Cat
за Animal
,
В PHP типы аргументов инвариантный при перегрузке методов, т.е. принятые типы должны точно соответствовать типу родительского. Хотя можно утверждать, что контравариантные типы имеют смысл, по техническим причинам это невозможно сделать.
Таким образом, решение является простым и ясным: поместите все в один файл, и строгие стандарты будут удовлетворены;)
Нет, и вам определенно не следует полагаться на такое поведение.
Это одна из многих странных форм поведения PHP
когда дело доходит до классов и пространств имен.
Стандартное решение заключается в создании Interface
(давайте назовем это FoodInterface') and implement it in the base class. Then use
FoodInterfaceas the type of the argument of method
кормить () `:
interface FoodInterface {}
abstract class Food implements FoodInterface {}
class Meat extends Food {}
abstract class Animal {
function feed(FoodInterface $food) {}
}
class Cat extends Animal {
function feed(FoodInterface $meat) {
parent::feed($meat);
}
}
FoodInterface
Интерфейс может быть пустым или вы можете объявить в нем функции, которые вам нужно вызвать Animal::feed()
,
Таким образом, вы можете feed()
ваш Cat
(или любой другой Animal
) с любым объектом, который реализует FoodInterface
независимо от того, расширяют ли они Food
класс или нет. Пока они реализуют интерфейс, они хороши для подачи на любой Animal
,
class Toy implements FoodInterface {}
$cat = new Cat();
$cat->feed(new Toy()); // He can't eat it but at least he will have some fun :-)
Поскольку ваш базовый класс абстрактный, он может действовать как вышеупомянутый интерфейс. Забудьте об интерфейсе и просто объявите Cat::feed()
с теми же типами аргументов, что и Animal::feed()
,
Затем в реализации Cat::feed()
ты можешь использовать instanceof
проверить, является ли тип полученного аргумента тем, который вам нужен (Meat
):
abstract class Food {}
class Meat extends Food {}
abstract class Animal {
function feed(Food $food) {}
}
class Cat extends Animal {
function feed(Food $meat) {
if (! $meat instanceof Meat) {
throw new InvalidArgumentException("Cats don't eat any Food. Meat is required");
}
// Here you are sure the type of $meat is Meat
// and you can safely invoke any method of class Meat
parent::feed($meat);
}
}
Первый подход — правильный способ сделать это. Второй подход имеет свои преимущества, и я рекомендую использовать его только тогда, когда первый подход по какой-то причине невозможен.