3

I am trying to extend a package with my own functional ability. But the package code has type hints in the function calls, to other classes that are part of the package. I am just looking for a way to modify the code.

More details about what I'm trying to do at https://laracasts.com/discuss/channels/general-discussion/type-hint-hell

I have tried changing the code to use interfaces and abstracts, but i cant seem to prevent the "Declaration of class .... must be compatible with"error.

This is what i'm trying to do in a nutshell.

The package has this type of setup.

class ClassA {}

class ClassB {
    public function makeClassA(ClassA $classA) : ClassA
    {
        return $classA;
    }
}

This is what I am trying to do.

class ClassANew {}

class ClassC extends ClassB {
    public function makeClassA(ClassANew $classA) : ClassANew
    {
        return $classA;
    }
}

I get the following error,

"PHP Fatal error: Declaration of ClassC::makeClassA(ClassANew $classA): ClassANew must be compatible with ClassB::makeClassA(ClassA $classA): ClassA"

I know I could just fork the code and strip out the locked classA from ClassB, but I was trying not to do that.

If i was going to fork the code, I looked at how to maintain the premise of the original code. So, I tried changing the ClassA references in ClassB to a ClassAInterface, but I get the same error.

Is what I'm trying to do possible?

recnac
  • 3,744
  • 6
  • 24
  • 46
Terre Porter
  • 63
  • 2
  • 9
  • Why not create your own implementation of the class, using the interface it most likely implements? That way, you'd still be abiding the contract defined in the (assumed) interface and be able to use whatever class uses what you're trying to extend? – rkeet Apr 09 '19 at 13:31
  • Possible duplicate of [Why is overriding method parameters a violation of strict standards in PHP?](https://stackoverflow.com/questions/13423494/why-is-overriding-method-parameters-a-violation-of-strict-standards-in-php) – Manuel Mannhardt Apr 09 '19 at 13:44
  • I have a solution if you can slightly modify the two classes, that does not create security problems – Gianfrancesco Aurecchia Apr 09 '19 at 13:56

4 Answers4

3

No, this is not possible to do.

Look here, for the reasons: Why is overriding method parameters a violation of strict standards in PHP?

Manuel Mannhardt
  • 2,191
  • 1
  • 17
  • 23
1

this is a little trick, but its peculiarity that does not give rise to safety problems, in the past or already used and tested.

I know it's not really what you needed but it solves your problem to the full while maintaining the safety and the forcing of the returns of the methods

class ClassA {}

class ClassB {
    public function makeClassA_ClassB(ClassA $classA) : ClassA
    {
        return $classA;
    }
    function __call($function_name, $argument){
        if ($function_name==="makeClassA" && $argument[0] instanceof ClassA ) return $this->makeClassA_ClassB($argument[0]);
    }
}
class ClassANew {}

class ClassC extends ClassB {
    public function makeClassA_ClassC(ClassANew $classA) : ClassANew
    {
        return $classA;
    }
    function __call($function_name, $argument){
        if ($function_name==="makeClassA" && $argument[0] instanceof ClassANew ) return $this->makeClassA_ClassC($argument[0]);
    }
}

$t=new ClassC();
$t2=new ClassANew();
var_dump($t->makeClassA($t2)); // object(ClassANew)#212 (0) { }

$t=new ClassB();
$t2=new ClassA();
var_dump($t->makeClassA($t2)); // object(ClassA)#212 (0) { }
0

Ok, so I finally figured out the problem. I had to keep the original referenced return types the same. After that it works fine now.

namespace Original;

class ClassExtra {}

class ClassA {
    public function __construct($container, ClassB $classB) {}
}

class ClassB {
    public function __construct(ClassExtra $classExtra) {}
}

class ClassC {
    public $classB;
    public $containers;
    public function __construct(ClassB $classB) {
        $this->classB = $classB;
    }
    public function container(string $container = 'default'): ClassA
    {
        $this->containers[$container] = new ClassA($container, $this->classB);
        return $this->containers[$container];
    }
}

Namespace Changes;

Class NewClassA extends \Original\ClassA {}
Class NewClassB extends \Original\ClassB {}
Class NewClassC extends \Original\ClassC {
    public function container(string $container = 'default'): \Original\ClassA
    {
        $this->containers[$container] = new NewClassA($container, $this->classB);
        return $this->containers[$container];
    }
}

$classC = new \Original\ClassC(new \Original\ClassB(new \Original\ClassExtra()));
var_dump(get_class($classC->container('test')));
/* string(15) "Original\ClassA" */

$classC = new NewClassC(new NewClassB(new \Original\ClassExtra()));
var_dump(get_class($classC->container('test')));
/* string(17) "Changes\NewClassA" */
Terre Porter
  • 63
  • 2
  • 9
0

That should be possible:

// just an example
class ClassA {} // the class within the used package
class ClassANew {} // your own class

// ClassB is the parent class, the one in the package
class ClassC extends ClassB {
    public function makeClassA(ClassA|ClassANew $classA)
    {
        return $classA;
    }
}

Make sure that each class/interface has correct inheritance.

More details: https://www.php.net/manual/en/language.oop5.variance.php

However, you MUST NOT totally change the logic, of the parent's function. There is an extend/implementation of the object for some reason.
If you want to just have a method that shares only the name, do not implement the new object as an child class of the previous class. The class should be only inherited from the previous class if it shares the purpose.
An example, of what I am trying to say:

There is no meaning to make a class 'Countie' that implements 'Countable' just to have a method 'count':

class Countie implements \Countable {
    private int $num = 0;
    public function count() {
        foreach ($i = 1; $i <= $this->num; $i++) {
          echo $i;
        }
    }
}
SIR
  • 49
  • 1
  • 9