Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

php strict error by including

Tags:

php

strict

It looks like strict errors do not occur if the classes are in one file like this:

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 that:

abstract class Food{}

class Meat extends Food{}                    

require 'Animal.php';
require 'Cat.php';

Strict standards error message is thrown:

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

If all is in one file even this is ok:

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 a Food, there shouldn't be a complain in the first place, right? So the solution is plain and clear: Put everything in one file and strict standards are satisfied ;)

Any hints appreciated

like image 831
mife Avatar asked Jun 04 '15 12:06

mife


2 Answers

Is this the intended behavior?

Unfortunately, yes. The intricacies that come into play with class declarations make it so that strict rules aren't always applied when they all occur in the same script; a single class per file doesn't exhibit this issue.

Because Meat is a Food, there shouldn't be a complain in the first place, right?

Wrong for two reasons:

  1. Meat is a smaller type than Food and therefore by only allowing the smaller type in your descendent class you're violating LSP; you can't substitute Cat for Animal.

  2. In PHP, argument types are invariant when overloading methods, i.e. the accepted types must match exactly that of the parent. While it could be argued that contravariant types make sense, due to technical reasons this can't be done.

So the solution is plain and clear: Put everything in one file and strict standards are satisfied ;)

No, and you should definitely not rely on that behaviour.

like image 128
Ja͢ck Avatar answered Oct 27 '22 09:10

Ja͢ck


It's one of the many strange behaviours of PHP when it comes to classes and namespaces.

The standard solution is to create an Interface (let's name it FoodInterface') and implement it in the base class. Then useFoodInterfaceas the type of the argument of methodfeed()`:

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 can be empty or you can declare in it the functions you need to call in Animal::feed().

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

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

Then, in the implementation of Cat::feed() 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 it. The second approach has its own advantages and I recommend using it only when the first approach is not possible for some reason.

like image 27
axiac Avatar answered Oct 27 '22 10:10

axiac