PHP warns inherited function declaration as incompatible - php

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.

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.

PHP Covariance with inherited class - Declarations incompatibles

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.

Class Group contains 1 abstract method and must therefore be declared abstract or implement the remaining methods

Hello i have a problem,
with my code, i don't know what im fix this code
Error
Fatal error: Class Group contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Aturan::cari) in D:\xampp\htdocs\tes\index.php on line 16
This is my code
<?php
interface Aturan {
public function cari($id);
}
trait Bantuan {
public function ubah_ke_string_json($str){
return NULL;
}
}
abstract class Aturan_abstrak implements Aturan {
use Bantuan;
}
class Group extends Aturan_abstrak {
protected $grup;
public function Group($grup) {
$this->grup = $grup;
}
}
$grup = new Group(array(
"C" => array("administrator"),
"A" => array("operator","staff")
));
echo $grup->cari("A");
?>
i'm confused, help me please.
This error means, that one or more abstract methods needs to be implemented first. In your case, method cari is not implemented.
Please take a look at topic Class Abstraction for more information.

PHP Class extending an Abstract cannot run a function that has the same name as the Abstract Class

This code fails. But I don't understand why. If I change the function lol to function anything else, it works. If I change the class of the Abstract it also works. See this execution: http://codepad.viper-7.com/Z9V67x
<?php
abstract class Lol{
abstract public function lol($external = false);
}
class Crazy extends Lol{
public function lol($external = false){
echo 'lol';
}
}
$new = new Crazy;
$new->lol('fdgdfg');
The error that comes back is:
Fatal error: Cannot call abstract method Lol::lol() in /code/Z9V67x on line 17
PHP Fatal error: Cannot call abstract method Lol::lol() in /code/Z9V67x on line 17
Doing some research on same name functions and classes, in PHP4 the same name meant a constructor. According to this https://stackoverflow.com/a/6872939/582917 answer, namespaced classes no longer treated functions with the same name as the constructor. BUt non namespaced classes do. Still why can't the constructors be replaced in the child instance?
check code below:
abstract class Lol{
public function __construct() { // only one constructor is really needed
}
abstract public function lol($external = false);
}
class Crazy extends Lol{
public function __construct() { // only one constructor is really needed
}
public function Crazy() { // only one constructor is really needed
}
public function lol($external = false) {
echo 'lol';
}
}
$new = new Crazy();
$new->lol('fdgdfg');
it works ok, why?
PHP is not very good with OOP, even latest version, it has PHP4 legacy, so lets try to understand what your code do:
it defined abstract function, which is understand by php as constructor and as abstract method
in sub class you're defining only method, but constructor is still abstract
this is why you're getting error during new - php really cannot find constructor
now check code above - it has 3 constructors, you can have any 1 and this code will work

abstract class conflict in php

I have this class:
abstract class Hotel
{
protected $util;
function __construct()
{
$this->util = new Utility();
}
function add(Validate $data, Model_Hotel $hotel){}
function delete(){}
function upload_image(array $imagedetails, $hotel_id){}
}
and a class that extends it
class Premium extends Hotel
{
function add(Model_Hotel $hotel)
{
$hotel->values;
$hotel->save();
}
function upload_image(array $imagedetails, $hotel_id)
{
$this->util->save_image($imagedetails, $hotel_id);
}
}
but then I get an error:
"declaration of Premium::add must be compatible with Hotel::add"
as you can see, I left out a parameter for the add() method, which was intentional
what OOP facility would allow me to inherit a function whose parameters I can change? (Obviously an abstract class won't do here)
It's an E_STRICT error. In PHP you can't overload methods (that's the OOP paradigm you're looking for), so your signature must be identical to the abstract version of the method, or else it's an error.
You could make your $data parameter optional
class Hotel {
function add( Model_Hotel $hotel, Validate $data = null );
}

Categories