Php severe error by including

Severe errors do not appear if the classes are in the same file as follows:

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

But if you put the class definition in separate files and include them like this:

 abstract class Food{} class Meat extends Food{} require 'Animal.php'; require 'Cat.php'; 

Strict standards error message:

Strict standards: Cat::feed() declaration must be compatible with Animal::feed(Food $food) in c: \ path \ to \ Cat.php on line ...

If everything is in one file, this is normal:

 class Dog extends Animal{ function feed($qty = 1){ for($i = 0; $i < $qty; $i++){ $Meat = new Meat(); parent::feed($Meat); } } } 

Is this the intended behavior?

Because Meat is Food , there should first and foremost be no complaints, right? So, the solution is clear and understandable: put everything in one file and follow strict standards;)

Any hints appreciated

+6
source share
2 answers

Is this the intended behavior?

Unfortunately yes. The intricacies that come into play with class declarations make strict rules not always apply when they all occur in the same script; one class for each file does not detect this problem.

Since meat is food, you shouldn’t complain first, right?

Incorrect for two reasons:

  • Meat is a smaller type than Food, and therefore, by only allowing a smaller type in your descendant class, you violate the LSP ; you cannot replace Cat with Animal .

  • In PHP, argument types are invariant under overload, i.e. accepted types must exactly match the parent. Although it can be argued that contravariant types make sense, for technical reasons this cannot be done.

So, the solution is clear and understandable: put everything in one file and follow strict standards;)

No, and you should definitely not rely on this behavior.

+3
source

This is one of many weird PHP behaviors when it comes to classes and namespaces.

The standard solution is to create an Interface (let's call it FoodInterface') and implement it in the base class. Then use FoodInterface') and implement it in the base class. Then use FoodInterface as the type of the argument of method feed () `:

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

The FoodInterface interface may be empty or you can declare in it the functions necessary to call Animal::feed() .

That way you can feed() your Cat (or any other Animal ) with any object that implements FoodInterface , whether it extends the Food class or not. As long as they implement the interface, they are good for submitting to any 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 :-) 

Since your base class is abstract, it can act as the aforementioned interface. Forget the interface and simply declare Cat::feed() with the same argument types as Animal::feed() .

Then, in the Cat::feed() implementation, you can use instanceof to check if the type of the received argument is the one you want ( 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); } } 

Comments

The first approach is the right way to do this. The second approach has its advantages, and I recommend using it only when, for some reason, the first approach is impossible.

+1
source

Source: https://habr.com/ru/post/988511/


All Articles