We know that PHP doesn't accept child methods with a different signature than the parent. I thought that was the same with constructors: The PHP documentation states that
This also applies to constructors as of PHP 5.4. Before 5.4 constructor signatures could differ.
However, it appears that inherited constructors still can differ in PHP versions > 5.4. For example the following code does not trigger any warnings or notices:
class Something { }
class SomeOtherThing { }
class Foo
{
public function __construct(Something $foo)
{
}
public function yay()
{
echo 'yay';
}
}
class Bar extends Foo
{
public function __construct($foo, SomeOtherThing $bar = null)
{
}
}
$x = new Bar(new Something());
$x->yay();
According to the documentation, the code should trigger an error, as the contructor signatures are different.
Tried this on PHP 5.6.4. Same effect with other versions.
So, what's up with that? Are differing constructor signatures still legal, despite of what the documentation says? Or is this a bug which will be fixed in later versions?
According to the documentation
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.
So, that is why you are not getting an error of level E_STRICT. Perhaps it will trigger something at a different level.
I think you somewhat misread the documentation, because it states:
Furthermore the signatures of the methods must match, i.e. the type
hints and the number of required arguments must be the same. For
example, if the child class defines an optional argument, where the
abstract method's signature does not, there is no conflict in the
signature.
You've defined an optional parameter, so it's ok.
Related
In this code sample, the interface doesn't seem to care whether the implementing method foo() checks for an array type parameter even if it explicitly type-hinted array only.
<?php
declare(strict_types = 1);
interface MyInterface
{
public function foo(array $foo);
}
class Bar implements MyInterface
{
public function foo($foo)
{
return $foo;
}
}
echo (new Bar)->foo('test'); // runs just fine as string
I would expect at least a fatal, incompatible interface error; but there's none.
My questions are:
Is this an expected behavior?
Should the interface not have type hints at all because it's not respected anyway?
Short answer:
This is expected behaviour since 7.2, and interfaces type hints are enforced to an extent; but implementing classes may omit the interface's type declaration (but can't declare a parameter type different from the one declared in the interface).
Long answer:
This was a change introduced on PHP 7.2.
If you try this in PHP where PHP_VERSION_ID >= 7 && PHP_VERSION_ID < 7.2 you get:
Fatal error: Declaration of Bar::foo($foo) must be compatible with MyInterface::foo(array $foo)
But on PHP_VERSION_ID >= 7.2 it "works". The explanation for the changes is documented here, and says:
Parameter type widening
Parameter types from overridden methods and from interface
implementations may now be omitted. This is still in compliance with
LSP, since parameters types are contravariant.
interface A {
public function Test(array $input); }
class B implements A {
public function Test($input){} // type omitted for $input }
You can omit the parameter type, but you can't declare an incompatible type.
E.g. if in your example you tried:
public function foo(string $foo)
{
return $foo;
}
It would fail all around.
A couple of links for further reading regarding this change:
The PR
A post where the PR is explained and defended
An example of developers taking advantage of the new functionality.
Just wondering why we have a E_STRICT/E_WARNING warning here:
class Node
{
public static function create($parent = null)
{
// ...
}
}
class NamedNode extends Node
{
public static function create($name, $parent = null)
{
// ...
}
}
As you can see in http://3v4l.org/n1s38, we have an E_STRICT (PHP < 7) or an E_WARNING (PHP ≥ 7).
I really can't see any reason for this...
Furthermore, if we make the $name argument optional (http://3v4l.org/V1WHC), no warning is thrown...
And that confuses me even more: if the method signatures must be the same (and as I said above I wonder why), why no warning is thrown?
EDIT: this is not a duplicate of Why is overriding method parameters a violation of strict standards in PHP? since here we are talking about static methods. I do think that non-static methods must have the same signature (since we'll work with instances in that case), but static methods are very different: we call the method statically, so we do know the class that get's called (eg Node::create() or NamedNode::create()).
I can understand why making the name parameter optional would stop the warning from appearing. The Node::create function takes 0 or 1 parameters, of any type (since you did not use type hints). If you make the name parameter optional in NamedNode::create it accepts 0, 1 or 2 parameters, again of any type. This means that all valid call signatures for Node::create are also valid for NamedNode::create. You only add an extra possibility but that could not break old code.
Apparantly it is possible to call a static function on an instance, which is the reason that this warning is shown even for static functions. See also https://3v4l.org/FPEUj
I have an abstract class I am inheriting from:
abstract class Test
{
public function GetTests()
{
}
}
and I have a concrete that I have been using the abstract classes implementation for most of the time:
class Concrete extends Test
{
// No problemmos
}
I recently had to implement a different version of the GetTests method, and in fact I wanted to overwrite it as it's built into all of my routing:
class Concrete extends Test
{
public function GetTests( $newArgument )
{
// notice $newArgument
}
}
However I get this error message:
Declaration of Concrete::GetTests() should be compatible with Test::GetTests()
Apart from copying the entirety of the functions from the abstract class for this concrete, even though I only need to implement this one method differently... Is there a way of getting around this?
I do understand that I could have:
abstract class Test
{
abstract public function GetTests();
}
But this is why I am snookered, because I no longer have the ability to modify how the underlying Test class is implemented... doh!... Unless I really have to..
Thanks to all great answers...
I have decided to de snooker myself (it's going to hurt but it's going to be worth it) and I will instantiate the Test class inside the Concrete class, implement concrete versions of all the Test class methods, and then inside them just call the instantiated Test class... This means in the future (or indeed now) I can simply not call that feature...
For context:
/* no longer abstract */ class UnitOfWorkController
{
public function GetUnits()
{
// Implementation
return View::make(...);
}
}
and...
class SomethingController /* no longer extends the UnitOfWorkController */
{
private $unitOfWorkController;
public function __Construct()
{
$this->unitOfWorkController = new UnitOfWorkController();
}
public function GetUnits()
{
return $this->unitOfWorkController->GetUnits();
// or I could just implement my own junk
}
}
Your concrete subclass is in violation of the Liskov Substitution Principle, which to cut a long story short says that if an object of class X can be processed by a given piece of code, then every possible subclass of X must also be able to be processed by the same piece of code.
Say I wanted to make another subclass of Test and wanted to implement my own GetTests method. The base class method doesn't accept any arguments at all, so that suggests that, if my subclass is to be substitutable for its superclass, my implementation of that method cannot take any arguments either. If I give my implementation arguments, then it no longer conforms to the specification as laid down by the superclass.
If I have code that does:
$object = new Test;
$test -> GetTests ();
then I can't substitute my subclass of Test without also changing the calling code to pass in an argument. Likewise if I do change it, then I have another subclass of Test that doesn't require an argument for GetTests then the code would have to change again. In fact the same code simply can't be used as is with both subclasses without having to jump through some hoops to determine the actual class and using the appropriate calling convention which means needing to know things about the class I'm about to use that I shouldn't need to know.
PHP is less strict than most OO languages about subclass method signitures matching their superclass, but it will issue a warning if they don't match. The only way to fix the warning is to have all subclasses have the same method signatures as the superclass they inherit from.
Child methods must have the same signature as the same method in a parent class. This includes required parameters and their typecasting.
For example, a child class of the following method must also have one parameter, and the parameter must cast to the ArgumentType class or a child of thereof.
public function something(ArgumentType $Argument)
{
}
You can, however, make the parameter optional by setting it to null or any other value:
public function something(ArgumentType $Argument = null)
{
}
In this case, child methods may omit this parameter.
From the PHP docs, see http://php.net/manual/en/language.oop5.abstract.php:
[…] Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. For example, if the child class defines an optional argument, where the abstract method's signature does not, there is no conflict in the signature.
The method signature of Concrete::GetTests() has a variable while Test::GetTests() does not. Since you have already defined this method within Test, it is now being inherited. The inherited version is not compatible with your overridden version.
Here are your options:
Add $newArgument to the parameters list in Test::GetTests().
Remove $newArgument from the parameters list in Concrete::GetTests().
Rename Concrete::GetTests() to something else.
PHP does not support this, as the error message says. If you want to override the function, it has to have the same footprint, which in your case it doesn't
What you could do is use a magic method: http://php.net/manual/en/language.oop5.overloading.php#object.call
the parameter array is a separate entity, so you 'decide' in your code (which you can override) what to do with which parameter.
I wanted to link a blog I read about this, but couldn't find the one I was thinking of. There is this rather strangely formatted one, not sure if it any good, but it does touch on some of the issues.
You could obviously add the argument to the parent, but this is 'leaking' upwards. If other childeren want even more, you'd get a big party of random paramteres that all can be nulled.
Before PHP 5.3.3 the following
class Same {
public function same() { echo 'Not good'; }
}
$c = new Same();
will output Not good.
From 5.3.3+ instead it will not output the string. That's because from PHP 5.3.3 functions with the same name of the class are not considered constructors.
How do I force this behavior even with PHP 5.3.2 or before?
The easiest way is probably just to create an empty constructor:
class Same {
public function same() { echo 'Not good'; }
public function __construct() { }
}
$c = new Same();
That won't echo "Not good" as the __construct() method overrides the "same name as class" method.
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, it will search for the old-style constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues is if the class had a method named __construct() which was used for different semantics.
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.
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
docs:- http://php.net/manual/en/language.oop5.decon.php
So obvious solution is to declare a __constructors method (even is an empty one)
The construct method is named __construct(). Simply call your same()-method inside __construct() if u wish to have the same name.
According to http://php.net/construct php tries to reserve backwards compatibility. In my opinion the "same" name means writing the method name case-sensitive (as the class name). That should work too.
It appears (PHP 5.3) that if you are overriding a class method, it is okay to you can add additional parameters, as long as they have default values.
For example, consider the class:
class test1 {
public function stuff() {
echo "Hi";
}
}
The following class extends "test1" and will produce an E_STRICT warning.
class test2 extends test1 {
public function stuff($name) {
echo "Hi $name";
}
}
But, the following does not produce an E_STRICT warning.
class test3 extends test1 {
public function stuff($name = "") {
echo "Hi $name";
}
}
While class "test3" doesn't produce an E_STRICT warning, I have been under the impression that PHP does not allow method signatures to be overloaded overridden. So, I have to ask. Is my observation a bug/flaw or actually correct intended behavior?
Further, if a default argument parameter is okay, why is a non-default argument parameter not okay?
If you add a non-defaulted parameter to an overridden method, the subclass no longer satisfies the contract defined by the superclass. You cannot correctly call test2->stuff(), because this method now expects a parameter - but the superclass says you should be able to call it without one. Hence the E_STRICT warning.
If you add a defaulted parameter though, you can still call test3->stuff() (from your example) - as the superclass expects - and so the contract is not broken. In fact, by adding the optional parameter, you have simply extended it.
This is not a bug and is acceptable PHP programming practise.
Please note that multiple overrides can cause programmer headaches though and should be avoided where possible.
Alternatively I usually either have a single extended class to override per class or just overload a class method within the actual class itself.