43

I know there are a couple of similar questions here in StackOverflow like this question.

Why is overriding method parameters a violation of strict standards in PHP? For instance:

class Foo
{
    public function bar(Array $bar){}
}

class Baz extends Foo
{
    public function bar($bar) {}
}

Strict standards: Declaration of Baz::bar() should be compatible with that of Foo::bar()

In other OOP programming languages you can. Why is it bad in PHP?

Community
  • 1
  • 1
Kaern Stone
  • 619
  • 1
  • 5
  • 11

4 Answers4

58

In OOP, SOLID stands for Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion.

Liskov substitution principle states that, in a computer program, if Bar is a subtype of Foo, then objects of type Foo may be replaced with objects of type Bar without altering any of the desirable properties of that program (correctness, task performed, etc.).

In strong-typed programming languages, when overriding a Foo method, if you change the signature in Bar, you are actually overloading since the original method and the new method are available with different signatures. Since PHP is weak typed, this is not possible to achieve, because the compiler can't know which of the methods you are actually calling. (hence the reason you can't have 2 methods with the same name, even if their signatures are different).

So, to avoid the violation of Liskov Substituition principle, a strict standard warning is issued, telling the programmer something might break due to the change of the method signature in the child class.

Tivie
  • 18,864
  • 5
  • 58
  • 77
  • 28
    I have to disagree with two things here: "if Bar is a subtype of Foo, then objects of type Foo may be replaced with objects of type Bar (and vice-versa)". The vice-versa part doesn't hold; you don't expect to use a superclass in any place where a sublcass is used. The other thing is about LSP: if the parameters are contravariant then the types are respected. If PHP was pure OO then there would be no problem, since Array would be narrower than Object (assuming that no type in the declaration means any object). Thus the implementation of bar() would be more general in Baz, making it fit the LSP – Andrés Fortier Nov 16 '12 at 20:11
  • 1
    @AndrésFortier With vice-versa I meant that in the scope of Foo, Foo and Bar should be interchangeable. Regarding the second topic, however, I agree with you. The example Kaern gave does not violate LSP since Baz::bar() can safely substitute Foo::bar(). – Tivie Nov 16 '12 at 20:30
  • 3
    Very good explanation, thanks. Then why can I break the Liskov Principle in the constructor? – Kaern Stone Nov 16 '12 at 20:34
  • @KaernStone: LSP should hold always, constructors shouldn't be a special case. I may be wrong here, but this can be one of the many PHP caveats. Please remember that in PHP there isn't a formal type system, just type hinting. – Andrés Fortier Nov 16 '12 at 20:39
  • 3
    @Tivie: Please, don't take this personal, but I think that this answer like this is misleading. I'll downvote it. – Andrés Fortier Nov 16 '12 at 20:40
  • Actually, there is discussion if LSP should apply to constructors. PHP does not think so. But that is a new question altogether. – Tivie Nov 16 '12 at 20:43
  • 4
    @KaernStone LSP does not apply to Ctors because that would limit polymorphism. Different subtypes might need different dependencies. Think `Log` and subtypes `DbLogger` and `FileLogger`. – Gordon Jun 26 '13 at 18:30
  • 1
    @AndrésFortier https://bugs.php.net/bug.php?id=71825&thanks=4 It would be nice if this would be fixed in PHP.. – donquixote Mar 14 '16 at 22:33
  • 3
    @AndrésFortier "Thus the implementation of bar() would be more general in Baz, making it fit the LSP". A subclass can an should only be more specific, not more general. Besides, you other statement "LSP should hold always, constructors shouldn't be a special case." can be explained. The reason constructors don't have to obey LSP is that the creation of a new object is never interchangeable. Even if you put it in a factory method, you either create A or you create B in the end. There is no polymorphism in object creation. – David Jun 27 '16 at 08:43
  • @AndrésFortier I will soon have posted so many "bug reports" that I may actually make myself familiar with how to write RFCs :) – donquixote Jul 11 '16 at 21:15
  • @DavidMaes Yes, a subclass should be more specific. For that to happen, return values must be covariant (i.e. more specific) and parameters contravariant (i.e. more general). Comments are not the place to make a long explanation, but if you check the LSP you will see what I mean. About constructors, in pure OO languages (e.g. ruby or Smalltalk) classes are just objects, so the LSP still holds. And even if a class is not an object (and your language of choice does not enforce LSP) following it is a good practice IMHO. HTH. – Andrés Fortier Jul 11 '16 at 21:23
19

I know I am late to the party but the answers don't really spell out the actual problem.

The problem is PHP doesn't support function/method overloading. It would be difficult to support function overloading in an untyped language.

Hinting helps. but in PHP it is very limited. Not sure why. For example you cannot hint a variable is an int or Boolean yet array is fine. Go figure!

Other object orientated languages implement this using function overloading. Which is to say the signature of the function is obviously different.

So for example if the following was possible we would not have an issue

class Foo
{
    public function bar(Array $bar){
        echo "Foo::bar";
    }
}

class Baz extends Foo
{
    public function bar(int $bar) {
        echo "Baz::bar";
    }
}


$foo = new Baz();
$bar = new Baz();
$ar = array();
$i = 100;

$foo->bar($ar);
$bar->bar((int)$i);

would output

Foo::bar
Baz::bar

Of course when it came to constructors the php developers realised they have to implement it, Like it or not! So they simply suppress the error or not raise it in the first case.

Which is silly.

An acquaintance once said PHP implemented objects only as a way of implementing namespaces. Now I am not quite that critical but some of the decisions taken do tend to support that theory.

I always have maximum warnings turned on when developing code, I never let a warning go by without understanding what it means and what the implications are. Personally I don't care for this warning. I know what I want to do and PHP doesn't do it right. I came here looking for a way to selectively suppress it. I haven't found a way yet.

So I will trap this warning and suppress it myself. Shame I need to do this. but I am strict about STRICT.

DeveloperChris
  • 3,412
  • 2
  • 24
  • 39
  • 2
    With regards to constructors, in PHP 5.4+ the method signatures must be compatible when extending an _abstract_ base class, otherwise you get a fatal error. A bit strange that before PHP 5.4 there wasn't even an E_STRICT warning, and if the base class is not abstract then there are no messages regardless of PHP version. – MrWhite Jul 22 '14 at 19:56
  • Thank you thank you thank you. This is the first and only place I've seen word of PHP purposely suppressing errors in the constructor, a very nasty gotcha. This is a violation of the LSP, as allowing a subtype argument in the constructor is a case of strengthening the preconditions. Method arguments should only ever be invariant or contravariant, not covariant. In fact, in PHP, I believe this is the only scenario where anything other than invariance of typed arguments in overridden methods is possible. – e_i_pi Dec 08 '16 at 04:39
4

You can override parameters, but the signature should match. If you had put Array out in front of $bar, there would be no problem.

For example, if you had added an additional parameter, there would be no problem, provided the first parameter had the same type hinting. This is good practice in any language.

Brad
  • 159,648
  • 54
  • 349
  • 530
  • But why should the signature match? In Java I can change the signature – Kaern Stone Nov 16 '12 at 20:11
  • 1
    @KaernStone, You can in PHP as well, but you shouldn't. Suppose you have your subclass of `Foo` called `Baz`, and some other code wants to interact with an instance of `Baz` as if it were a `Foo`. It should be able to do that without modification, because `Baz` **is** a `Foo`. PHP will allow you to break this principal, but you shouldn't unless you have a very good reason for doing so. – Brad Nov 16 '12 at 20:20
  • 2
    @Brad, please see my comments in Tivie's answer. If not putting a type in the parameter means that the method can take any object it means that they are contravariant and thus a Baz **is-a** Foo. There should be no problem form the typing system point of view. Please, see [here](http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29) – Andrés Fortier Nov 16 '12 at 20:46
  • @AndrésFortier, That is a good point, I see what you are getting at in this specific instance. – Brad Nov 16 '12 at 21:01
  • 1
    _"For example, if you had added an additional parameter, there would be no problem, provided the first parameter had the same type hinting."_ This is wrong. If you declared this in the `Baz`/child class `public function bar(Array $bar, $baz) {}` it would throw the compatibility error. – Katrina Jul 12 '17 at 21:10
2

Because you declared on Foo that $bar should be of type array, while in the extending Bar, $bar's type isn't declared.

This isn't an error, it's a warning. You should make the method definition compatible with the original, base class. You can, however, safely ignore it if you know what you're doing (and only if you know what you're doing!!!)

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • yes, I know. But why should I make the method definition compatible with the original, base class? – Kaern Stone Nov 16 '12 at 20:12
  • 1
    @KaernStone: Because other classes/methods/applications rely on that original interface, and if you change it, you may break compatibilty. – Madara's Ghost Nov 16 '12 at 20:16