PHP Covariance with inherited class - Declarations incompatibles - php

I would like create an abstract class with abstract method which allow abstract type in return type. In my final class, I would like override type returned with type which implement abstract type initially declared.
<?php
abstract class A {
abstract public function test(A $foo): self;
}
class B extends A {
public function test(B $foo): self
{
return $this;
}
}
This compilation error is thrown:
Fatal error: Declaration of B::test(B $foo): B must be compatible with A::test(A $foo): A in ... on line 8
In documentation, covariance is explained with interface. But not with abstract class. More about PHP implementation, the documentation say:
In PHP 7.2.0, partial contravariance was introduced by removing type restrictions on parameters in a child method. As of PHP 7.4.0, full covariance and contravariance support was added.
I'm using PHP 7.4.

A quite core principle of object oriented programming is the Liskov substitution principle which essentially boils down to:
if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program
The way to achieve this is by having covariant method return types, contravariant method type arguments. Exceptions thrown kind of count as return types here so they also need to be covariant.
What you need is covariance of type arguments which breaks this principle.
The reason why can be seen by considering the example below:
abstract class A {
abstract public function test(A $foo): self;
}
class C extends A {
public function test(C $foo): self {
return $this;
}
}
class B extends A {
public function test(B $foo): self {
return $this;
}
}
$b = new B();
$c = new C();
$b->test($c); // Does not work
((A)$b)->test((A)$c); // Works
In the example above, you don't allow B::test to accept any type other than B as a type argument. However since B itself is a child of A and C is also a child of A by simple down-casting (which is allowed) the restriction is bypassed. You could always disable down-casting but that's almost saying you're disabling inheritance which is a core principle of OOP.
Now of course there are compelling reasons to allow covariance of type arguments, which is why some languages (such as e.g. Eiffel) allow it, however this is recognised to be a problem and even has been given the name CATcalling (CAT stands for Changed Availability or Type).
In PHP you can attempt to do runtime checks to remedy this situation:
abstract class A {
abstract public function test(A $foo) {
// static keyword resolve to the current object type at runtime
if (!$foo instanceof static) { throw new Exception(); }
}
}
class C extends A {
public function test(A $foo): self {
parent::test($foo);
return $this;
}
}
class B extends A {
public function test(A $foo): self {
parent::test($foo);
return $this;
}
}
However this is a bit messy and possibly unnecessary.

Related

Strict Standards: Declaration of item issue [duplicate]

Strict Standards: Declaration of childClass::customMethod() should be compatible with that of parentClass::customMethod()
What are possible causes of this error in PHP? Where can I find information about what it means to be compatible?
childClass::customMethod() has different arguments, or a different access level (public/private/protected) than parentClass::customMethod().
This message means that there are certain possible method calls which may fail at run-time. Suppose you have
class A { public function foo($a = 1) {;}}
class B extends A { public function foo($a) {;}}
function bar(A $a) {$a->foo();}
The compiler only checks the call $a->foo() against the requirements of A::foo() which requires no parameters. $a may however be an object of class B which requires a parameter and so the call would fail at runtime.
This however can never fail and does not trigger the error
class A { public function foo($a) {;}}
class B extends A { public function foo($a = 1) {;}}
function bar(A $a) {$a->foo();}
So no method may have more required parameters than its parent method.
The same message is also generated when type hints do not match, but in this case PHP is even more restrictive. This gives an error:
class A { public function foo(StdClass $a) {;}}
class B extends A { public function foo($a) {;}}
as does this:
class A { public function foo($a) {;}}
class B extends A { public function foo(StdClass $a) {;}}
That seems more restrictive than it needs to be and I assume is due to internals.
Visibility differences cause a different error, but for the same basic reason. No method can be less visible than its parent method.
if you wanna keep OOP form without turning any error off, you can also:
class A
{
public function foo() {
;
}
}
class B extends A
{
/*instead of :
public function foo($a, $b, $c) {*/
public function foo() {
list($a, $b, $c) = func_get_args();
// ...
}
}
Just to expand on this error in the context of an interface, if you are type hinting your function parameters like so:
interface A
use Bar;
interface A
{
public function foo(Bar $b);
}
Class B
class B implements A
{
public function foo(Bar $b);
}
If you have forgotten to include the use statement on your implementing class (Class B), then you will also get this error even though the method parameters are identical.
I faced this problem while trying to extend an existing class from GitHub. I'm gonna try to explain myself, first writing the class as I though it should be, and then the class as it is now.
What I though
namespace mycompany\CutreApi;
use mycompany\CutreApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function whatever(): ClassOfVendor
{
return new ClassOfVendor();
}
}
What I've finally done
namespace mycompany\CutreApi;
use \vendor\AwesomeApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function whatever(): ClassOfVendor
{
return new \mycompany\CutreApi\ClassOfVendor();
}
}
So seems that this errror raises also when you're using a method that return a namespaced class, and you try to return the same class but with other namespace. Fortunately I have found this solution, but I do not fully understand the benefit of this feature in php 7.2, for me it is normal to rewrite existing class methods as you need them, including the redefinition of input parameters and / or even behavior of the method.
One downside of the previous aproach, is that IDE's could not recognise the new methods implemented in \mycompany\CutreApi\ClassOfVendor(). So, for now, I will go with this implementation.
Currently done
namespace mycompany\CutreApi;
use mycompany\CutreApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function getWhatever(): ClassOfVendor
{
return new ClassOfVendor();
}
}
So, instead of trying to use "whatever" method, I wrote a new one called "getWhatever". In fact both of them are doing the same, just returning a class, but with diferents namespaces as I've described before.
Hope this can help someone.

What is wrong with this example of class inheritance? [duplicate]

This question already has answers here:
Override method parameter with child interface as a new parameter
(2 answers)
Abstract function parameter type hint overriding in PHP 7
(2 answers)
Closed 3 years ago.
This code of dependency inversion should work fine but instead it gives error.
What am I doing wrong in here?
interface A { }
abstract class B implements A { }
class C extends B { }
abstract class D {
public function foo(A $a) { }
}
class E extends D {
public function foo(C $c) { }
}
The error is:
Warning: Declaration of E::foo(C $c) should be compatible with D::foo(A $a) in [...][...] on line 24
Surprisingly, doing the same for the constructor method works just fine:
interface A { }
abstract class B implements A { }
class C extends B { }
abstract class D {
public function __construct(A $a) { }
public function foo(A $a) { }
}
class E extends D {
public function __construct(C $c) { }
public function foo(A $c) { }
}
There are two questions here.
Why the first part raises a WARNING: the error is pretty explicit. Any time you override a method you need to respect the parent's signature, so a client application's expectations are met.
class Foo {}
class Bar extends Foo {}
class A {
public function test(Foo $foo) {}
}
class B extends A {
public function test(Bar $bar) {}
}
Now, since B extends A, and A::test() expects a Foo, a user might try to do this:
$a = new A();
$b = new B();
$foo = new Foo();
$bar = new Bar();
$b->test($bar);
$b->test($foo);
The first call to test() would work, but the second would fail (and fatally): a Foo is not a Bar.
The parameter type is more restrictive than the original, and thus breaks expectations from client applications. Note that if we had a legal declaration (B::test(Foo)), we could still call $b->test($bar) and would work as expected, so changing the signature is not really necessary.
In November 2019, When PHP 7.4 is released, contravariance will be supported for parameter types, and covariance will be supported for return types; so your example would still be invalid.
Finally, notice that the error raised is a WARNING, it won't crash the program immediately. But it is warning you that you are opening your application to behaviour that might crash it, and that's it's generally poor design (for the reasons explained above).
The second question: Why you do not get the same warning for the constructor? They do not raise this warning by design. If you check the docs, you'll find this:
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.
Why constructors are treated differently? Because it is generally understood that constructors are not part of the "public API" of an object, and do not need to be subjected to the same constraints. The Liskov Substitution Principle deals with objects, not with classes; and before an class is instantiated there is yet no object. Thus, the constructor is out of scope.
You can read more about this here, for example.
TL;DR C is implementing A, but it can also inherit other methods from B which implementing A doesn't guarantee. This creates situation where two classes of same inherited type can have two methods with different signatures. A sub-class must accept all inputs that the parent class would accept, but it can accept additional inputs if it wants,
Let's extend your example.
Nothing crazy, just some example methods.
interface A
{
public function gotA();
}
abstract class B implements A
{
abstract public function gotB();
}
class C extends B
{
public function gotA()
{
// ...
}
public function gotB()
{
// ...
}
}
I can agree that C is indirectly implementing A, but it also contains methods from B, so the call in E::foo() would be perfectly fine.
abstract class D
{
public function foo(A $a)
{
$a->gotA();
}
}
class E extends D
{
public function foo(C $c)
{
$c->gotB();
}
}
Another example class implementing A.
class ExampleA implements A
{
public function gotA()
{
// ...
}
}
Now the fun part. Assume this function anywhere else in your code. It assumes D (expecting A in foo) as parameter.
But E is also instanceof D and can be passed to x, but it expects C in foo which ExampleA has nothing to do with.
function x(D $d)
{
$exampleA = new ExampleA();
$d->foo($exampleA);
}
$e = new E();
x($e);
To sum up, the contract for function x has been met and yet the function broke.
You ended up with two 'different' D types. The one expecting A and the one expecting C.
As for why it doesn't throw warning with __constructor I don't know. Need further investigation.

php oop - force static method's param to be object of current class

I want to do something like this:
class myclass{
public static function(__class__ $param){
// do somthing
}
}
but __class__ and self::class and nor static is not working.
how should I do this
If you want to enforce this dynamically for subclasses, I'm afraid that's not possible. The best you can do in this case is something like this:
class myclass {
public static function doStuff(self $a) {
if (!$a instanceof static) {
throw new InvalidArgumentException(sprintf('First argument must be of type %s', static::class));
}
// Do things with $a
}
}
Just use self:
class A
{
public function foo(self $arg)
{
var_dump($arg);
}
}
$a = new A();
$a->foo($a); // object(A)#1 (0) { }
No but, guys, suppose the class is extended tho, he might want to make sure the call to this method uses the child class, not the parent
You can manually restrict this contract for child classes, as shown in other anwsers (instanceof, etc), but it violates the Liskov substitution principle.
A subclass should always satisfy the contract of the superclass. In good design you should not to narrow the type of arguments in overridden methods.
Widening is allowed: Contravariant method argument type and PHP 7.2 partially supports it.
What you're asking does not make that much sense in strongly typed languages so I don't think it is something you can do in PHP.
Consider this:
abstract class A {
public abstract function a(A $myTypeObject);
}
class B extends A {
public function a(B $myTypeObject) {
return $this;
}
}
Now the problem with this code is that it cannot exist because the overridden function must match the same signature as the parent function. In strongly typed languages a would in fact be an overload so B would have 2 declarations of a , one which accepts A and one which accepts B. However in PHP you'll just get a parse error. If you really want to enforce things you need runtime checking:
abstract class A {
public function a($myTypeObject) {
if (!($myTypeObject instanceof static)) {
throw new \Exception("Invalid type");
}
}
}

PHP warns inherited function declaration as incompatible

I have a strange warning concerning PHP strict standards in my debug.log.
PHP Strict standards: Declaration of gb_EntryList::fields() should be compatible with mgr_LinearModelCollection::fields($mKey = NULL) in ... on line 339
So far so bad, but line 339 holds the definition of the gb_EntryList class
class gb_EntryList extends mgr_mySQLModel
and gb_EntryList does not define fields() at all. It inherits this from mgr_mySQLModel:
abstract class mgr_mySQLModel extends mgr_LinearModelCollection implements mgr_mySQLModelUpdateable {
...
function fields($mKey = null) { ... }
}
Originally, I forgot to put the = null into the declaration, which produced similar messages about mgr_mySQLModel. But these are gone now.
The code runs nicely, but what does this message want to tell me?
PHP version:
PHP 5.4.4-14+deb7u5 (cli) (built: Oct 3 2013 09:24:58)
Update:
I dug a little deeper into the issue. Interestingly the following code should have the same structure, but is fine with php -d error_reporting=4095 -l:
abstract class A {
function foo($sText = ""){
echo "A.foo($sText)\n";
}
abstract function bar();
}
abstract class B extends A {
function foo($sText = ""){
echo "B.foo($sText)\n";
}
}
class C extends B {
function bar(){
echo "C.bar()\n";
}
}
$c = new C();
$c->foo('in C');
The actual classes are too big to reproduce them here. But the structure is apart from interface inheritence the same. However, the above code appears to be fine. The actual situation is slightly more complex:
abstract class mgr_LinearModelCollection implements IteratorAggregate, Countable, ArrayAccess {
protected $sItemClass;
function getIterator() { }
function count(){ }
function offsetExists($offset){ }
function offsetGet($offset){ }
function offsetUnset($offset){ }
function offsetSet($offset, $value){ }
function fields($mKey = null){
$aDummy = call_user_func(array($this->sItemClass,'dummyArray'),$mKey);
return array_keys($aDummy);
}
}
interface mgr_mySQLModelUpdateable {
static function updateTable(array $aOpts = array());
}
abstract class mgr_mySQLModel extends mgr_LinearModelCollection implements mgr_mySQLModelUpdateable{
protected $aFieldNames = null;
function fields($mKey = null){
if(!is_array($this->aFieldNames)) return false;
return $this->aFieldNames;
}
}
class gb_EntryList extends mgr_mySQLModel {
static function updateTable(array $aOpts = array()){ }
}
There are of course many more functions and the { } are filled with code, but apart from that this is the real unchanged code, which produces the said error message. I currently lack any idea, why the toy model is fine, but the real one is not. Any hints?
This E_STRICT message warns about a violation of the Liskov substitution principle. It means that a method that was already defined in the superclass needs to be callable with the arguments of the method in the superclass.
This will trigger the message:
class Super {
public function foo($argument) {
echo $argument;
}
}
class Child extends Super {
public function foo() {
parent::foo('bar');
}
}
Output:
Strict Standards: Declaration of Child::foo() should be compatible
with Super::foo($argument) in ... on line 15
The fact that you say that field() is not declared in gb_EntryList is the clue to what's wrong. mgr_mySQLModel is an ABSTRACT class, if you look in the PHP manual entry for abstract classes the clue is in the line that says "When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child". So although PHP is magically working out that you want the parent field() method, you really should also be declaring it in the child class too.

Declaration of Methods should be Compatible with Parent Methods in PHP

Strict Standards: Declaration of childClass::customMethod() should be compatible with that of parentClass::customMethod()
What are possible causes of this error in PHP? Where can I find information about what it means to be compatible?
childClass::customMethod() has different arguments, or a different access level (public/private/protected) than parentClass::customMethod().
This message means that there are certain possible method calls which may fail at run-time. Suppose you have
class A { public function foo($a = 1) {;}}
class B extends A { public function foo($a) {;}}
function bar(A $a) {$a->foo();}
The compiler only checks the call $a->foo() against the requirements of A::foo() which requires no parameters. $a may however be an object of class B which requires a parameter and so the call would fail at runtime.
This however can never fail and does not trigger the error
class A { public function foo($a) {;}}
class B extends A { public function foo($a = 1) {;}}
function bar(A $a) {$a->foo();}
So no method may have more required parameters than its parent method.
The same message is also generated when type hints do not match, but in this case PHP is even more restrictive. This gives an error:
class A { public function foo(StdClass $a) {;}}
class B extends A { public function foo($a) {;}}
as does this:
class A { public function foo($a) {;}}
class B extends A { public function foo(StdClass $a) {;}}
That seems more restrictive than it needs to be and I assume is due to internals.
Visibility differences cause a different error, but for the same basic reason. No method can be less visible than its parent method.
if you wanna keep OOP form without turning any error off, you can also:
class A
{
public function foo() {
;
}
}
class B extends A
{
/*instead of :
public function foo($a, $b, $c) {*/
public function foo() {
list($a, $b, $c) = func_get_args();
// ...
}
}
Just to expand on this error in the context of an interface, if you are type hinting your function parameters like so:
interface A
use Bar;
interface A
{
public function foo(Bar $b);
}
Class B
class B implements A
{
public function foo(Bar $b);
}
If you have forgotten to include the use statement on your implementing class (Class B), then you will also get this error even though the method parameters are identical.
I faced this problem while trying to extend an existing class from GitHub. I'm gonna try to explain myself, first writing the class as I though it should be, and then the class as it is now.
What I though
namespace mycompany\CutreApi;
use mycompany\CutreApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function whatever(): ClassOfVendor
{
return new ClassOfVendor();
}
}
What I've finally done
namespace mycompany\CutreApi;
use \vendor\AwesomeApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function whatever(): ClassOfVendor
{
return new \mycompany\CutreApi\ClassOfVendor();
}
}
So seems that this errror raises also when you're using a method that return a namespaced class, and you try to return the same class but with other namespace. Fortunately I have found this solution, but I do not fully understand the benefit of this feature in php 7.2, for me it is normal to rewrite existing class methods as you need them, including the redefinition of input parameters and / or even behavior of the method.
One downside of the previous aproach, is that IDE's could not recognise the new methods implemented in \mycompany\CutreApi\ClassOfVendor(). So, for now, I will go with this implementation.
Currently done
namespace mycompany\CutreApi;
use mycompany\CutreApi\ClassOfVendor;
class CutreApi extends \vendor\AwesomeApi\AwesomeApi
{
public function getWhatever(): ClassOfVendor
{
return new ClassOfVendor();
}
}
So, instead of trying to use "whatever" method, I wrote a new one called "getWhatever". In fact both of them are doing the same, just returning a class, but with diferents namespaces as I've described before.
Hope this can help someone.

Categories