29

Here's a couple of snippets:

  1. Overriding constructor method has an extra parameter.

    class Cat {
        function __construct() {}
    }
    
    class Lion extends Cat {
        function __construct($param) {}
    }
    
  2. Overriding (regular) method has an extra parameter.

    class Cat {
        function doSomething() {}
    }
    
    class Lion extends Cat {
        function doSomething($param) {}
    }
    

The first would work, while the second would throw Declaration of Lion::doSomething() should be compatible with that of Cat::doSomething().

Why the special attitude towards constructor methods?

Emanuil Rusev
  • 34,563
  • 55
  • 137
  • 201

5 Answers5

37

To understand why they are treated differently, you have to understand Liskov's Substitution Principle, which states

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T." - BarbaraLiskov, Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (May, 1988).

In a nutshell this means any class using your Lion or Cat should be able to reliably call doSomething on it, regardless of the class being one or the other. If you change the method signature, this is no longer guaranteed (you may widen it, but not narrow it though).

Very simple Example

public function doSomethingWithFeline(Cat $feline)
{
    $feline->doSomething(42);
}

Since Lion extends Cat, you established an is-a relationship, meaning doSomethingWithFeline will accept a Lion for a Cat. Now imagine you add a required argument to doSomething in Lion. The code above would break because it is not passing that new param. Hence, the need for compatible signatures.

LSP does not apply to constructors though, because subtypes might have different dependencies. For instance if you have a FileLogger and a DBLogger, the ctors (constructors) of the first would require a filename, while the latter would require a db adapter. As such, ctors are about concrete implementations and not part of the contract between classes.

Community
  • 1
  • 1
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Will it be forever like this, still in 2018, constructors can have different signatures, which is good for what I am doing.. – tonix Apr 10 '18 at 14:18
12

The Liskov Substitution Principle states that "if S is a subtype of T, then objects of type T may be replaced with objects of type S". In your example it means that you should be able to replace objects of type Cat with objects of type Lion.

This is the reason your second code is not allowed. You would no longer be able to do this substitution as you would no longer be able to call the ->doSomething() method without arguments.

The constructor on the other hand is not subject to the Liskov Substitution Principle, as it is not part of the resulting object API. You will still be able to substitute the resulting objects, regardless of whether the constructor signatures match.

It is actually quite common that subclasses have more constructor arguments, because they are more specific and need more dependencies.

NikiC
  • 100,734
  • 37
  • 191
  • 225
5

The __construct() as they are unique per class. The constructor for Lion is not the same constructor for Cat, although if Lion extends Cat and Lion did not have a __construct(), you could still extend the parent __construct() with Lion::__construct().

Unlike with other methods, PHP will not generate an E_STRICT level error message when __construct() is overridden with different parameters than the parent __construct() method has.

PHP Manual: Constructors and Destructors

The other Magic Methods take specific arguments, which means their argument counts, etc, will always be consistent.

After the classes are instantiated, then does polymorphism/overiding kick in for your doSomething(). Your parent class, like how an abstract class does, defines arguments and visibility which needs to be matched by the child class to support overriding.

Mike Mackintosh
  • 13,917
  • 6
  • 60
  • 87
2

The constructor is for the concrete object only. It gives birth to it.

The other methods are - as long as the subtype is concerned - in relation to the inherited class therefore those methods are part of the same object and therefore should be compatible when spread over the definition of multiple classes.

Otherwise you create an object that is a bit shizo.


Edit: If you also want to introduce strict checks with the constructor signature you can make use of an interface since PHP 5.2 which added constructor checks:

Added support for constructors in interfaces to force constructor signature checks in implementations. Starting with PHP 5.2.0, interfaces can have constructors. However, if you choose to declare a constructor in an interface, each class implementing that interface MUST include a constructor with a signature matching that of the base interface constructor. By 'signature' we mean the parameter and return type definitions, including any type hints and including whether the data is passed by reference or by value.

(take from: Other Enhancements - Migrating from PHP 5.1.x to PHP 5.2.x)

hakre
  • 193,403
  • 52
  • 435
  • 836
1

What you are describing is overloading which is not supported by PHP. Now when you are creating a constructor for a class, it is only used within that class and the parent constuctor is not called by default (see Constructors and Destructors. You need to call it manually parent::__construct().

When you create a method in your class it does access the parent method directly.

So in conclusion:

  • the constuctor isnt using overloading but is only for the class itself
  • the method is using overloading and therefor not allowed
Hugo Delsing
  • 13,803
  • 5
  • 45
  • 72