I think there is a problem in php's OOP implementation.
EDIT: Consider more illustrative example:
abstract class Animal {
public $name;
// public function Communicate(Animal $partner) {} // Works
public abstract function Communicate(Animal $partner); // Gives error
}
class Panda extends Animal {
public function Communicate(Panda $partner) {
echo "Hi {$partner->name} I'm a Panda";
}
}
class Human extends Animal {
public function Communicate(Human $partner) {
echo "Hi {$partner->name} I'm a Human";
}
}
$john = new Human(); $john->name = 'John';
$mary = new Human(); $mary->name = 'Mary';
$john->Communicate($mary); // should be ok
$zuzi = new Panda(); $zuzi->name = 'Zuzi';
$zuzi->Communicate($john); // should give error
The problem is that when Animal::Communicate is an abstract method, php tells that the following methods are illegal:
"public function Communicate(Panda $partner)"
"public function Communicate(Human $partner)"
but when Animal::Communicate is non-abstract but has zero-implementation Php thinks that these methods are legal. So in my opinion it's not right because we are doing override in both cases, and these both cases are equal, so it seems like it's a bug...
Older part of the post:
Please consider the following code:
Framework.php
namespace A
{
class Component { ... }
abstract class Decorator {
public abstract function Decorate(\A\Component $component);
}
}
Implementation.php
namespace B
{
class MyComponent extends \A\Component { ... }
}
MyDecorator.php
namespace A
{
class MyDecorator extends Decorator {
public function Decorate(\B\MyComponent $component) { ... }
}
}
The following code gives error in MyDecorator.php telling
Fatal error: Declaration of MyDecorator::Decorate() must be compatible with that of A\Decorator::Decorate() in MyDecorator.php on line ...
But when I change the Framework.php::Decorator class to the following implementation:
abstract class Decorator {
public function Decorate(\A\Component $component) {}
}
the problem disappears.
I'm not sure (haven't tested it ;), but you declare this abstract function:
public abstract function Decorate(\A\Component $component);
So you should implement this EXACTLY like that. But you did this:
public function Decorate(\B\MyComponent $component) { ... }
That's not the same. Could you try to change that to \A\Component?
To all comments: fact of the matter is that this piece of PHP "runs"
namespace A
{
class Component { }
abstract class Decorator {
public abstract function Decorate(\A\Component $component);
}
}
namespace B
{
class MyComponent extends \A\Component { }
}
namespace A
{
class MyDecorator extends Decorator {
public function Decorate(\A\Component $component) {}
}
}
And this doesn't:
<?php
namespace A
{
class Component { }
abstract class Decorator {
public abstract function Decorate(\A\Component $component);
}
}
namespace B
{
class MyComponent extends \A\Component { }
}
namespace A
{
class MyDecorator extends Decorator {
public function Decorate(\B\MyComponent $component) {}
}
}
?>
With this error: PHP Fatal error: Declaration of A\MyDecorator::Decorate() must be compatible with that of A\Decorator::Decorate() in line 18
Now you can discuss all you like about how that should or should not be, but that's the problem with the code.
so, to satisfy my own curiosity: this is illegal too:
<?php
class Component { }
abstract class Decorator {
public abstract function Decorate(Component $component);
}
class MyComponent extends Component { }
class MyDecorator extends Decorator {
public function Decorate(MyComponent $component) {}
}
?>
It's not the namespaces or anything. It just doesn't seem legal.
See http://bugs.php.net/bug.php?id=36601, this issues has been reported as a bug but was rejected because of laziness :D
It has nothing to do with it being abstract. It has to do with the type hinting. The two definitions of the method are not compatible because you explicitly set the argument to be of type \A\Component and then try to overload the method with \B\Component you cant do that because it changes the method signature. Any subsequent declaration of Decorate must use the same type hint as its parent declaration in order for the method signatures to be compatible.
This might assist someone, and am not late.
The best way to handle such is by using an interface.
Consider below;
<?php
interface Componentor{}
class Component implements Componentor { }
abstract class Decorator {
public abstract function Decorate(Componentor $component);
}
class MyComponent extends Component { }
class MyDecorator extends Decorator {
public function Decorate(Componentor $component) {}
}
?>
Usage;
<?php
$c=new Component();
//TODO ....blah blah blah...
$decor=new MyDecorator();
//TODO ....blah blah blah...
$decor->Decorate($c);
?>
Related
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?
No, this is not possible to do.
Look here, for the reasons: Why is overriding method parameters a violation of strict standards in PHP?
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) { }
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" */
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;
}
}
}
I have function like this:
class Bar{
public function a():Foo{
.
.
.
}
}
now I am trying to create a mock for the class Bar with php unit test
$mockedBar = $this->getMockBuilder(Bar::class)
->getMock()
->method('a')
->willReturn(new FakeFoo());
but when I am calling method a I am getting an error that method a return type must be instance of Foo not Mocked_blahblah.
unfortunately class Bar don't use any interface and the system is very big and I can't create an interface cause it make huge refactor in my codes;
is there any way to disable return type of function a in mocked object?
I am useing php7.2 and phpunit 6.0.13.
Here is a real scenario:
class A
{
public function b():B
{
echo "i am from class A function b";
}
}
class B
{
}
class FakeB
{
}
class ATest extends TestCase
{
public function testSayHi(){
$mockedA = $this->getMockBuilder(A::class)
->getMock();
$mockedA->method('b')->willReturn(new FakeB());
$mockedA->b();
}
}
You can't disable return types. Perhaps you could try to do it with some kind of a hackish error handler, but it's a crazy thing to do.
Good news is that you're not trying to do anything unusual and your tests can be fixed.
Firstly, you need to assign the result of getMock to a variable. Next, you can define your test double:
class MyTest extends TestCase
{
public function testIt()
{
$mockedBar = $this->getMockBuilder(Bar::class)->getMock();
$mockedBar
->method('a')
->willReturn(new FakeFoo());
$this->assertInstanceOf(Foo::class, $mockedBar->a());
}
}
This will only work if FakeFoo is of type of Foo, for example extends it:
class FakeFoo extends Foo
{
// override any Foo methods you'd like to fake
}
You don't need to create a Fake yourself, you can use PHPUnit to create a dummy:
class MyTest extends TestCase
{
public function testIt()
{
$mockedBar = $this->createMock(Bar::class);
$mockedBar
->method('a')
->willReturn($this->createMock(Foo::class));
$this->assertInstanceOf(Foo::class, $mockedBar->a());
}
}
To fix your second example:
class A
{
public function b():B
{
echo "i am from class A function b";
}
}
class B
{
}
class FakeB extends B
{
}
class ATest extends TestCase
{
public function testSayHi(){
$mockedA = $this->getMockBuilder(A::class)->getMock();
$mockedA->method('b')->willReturn(new FakeB());
$mockedA->b();
}
}
Or, instead of using a fake let phpunit handle it:
class ATest extends TestCase
{
public function testSayHi(){
$mockedA = $this->getMockBuilder(A::class)->getMock();
$dummyB = $this->createMock(B::class);
$mockedA->method('b')->willReturn($dummyB);
$mockedA->b();
}
}
I have a problem in design my project code.
For example:
I have two model class: AClass and BClass which extends from CClass class.
For each of this class I have two service layer: AService and BService in which I have methods:
create(AClass $object) and create(BClass $object) corresponding.
So what I want. I want create Abstract Service CService in which I have abstract class create(CClass class). And my AService and BService must extends fromCService.
But in PHP I can't do this cause it failed for compatible exception.
So maybe some ideas how I can resolve this problem?
Update:
Maybe not very good example above, so I show you in real case:
abstract class Contract
{
protected $id;
protected $owner
}
class FundTraderContract extends Contract
{
private $fundContract;
}
class InvestorTraderContract extends Contract
{
private $investmentContract;
}
abstract class ContractService
{
abstract function create(Contract $contract){}
}
class FundTraderContractService extends ContractService
{
public function create(FundTraderContract $contract)
{
//some logic
}
}
class InvestorTraderContractService extends ContractService
{
public function create(InvestorTraderContract $contract)
{
//some logic
}
}
I think now example more easy-to-see. So in this case there is a compatible problem.
First off, apologies if this isn't exactly what you want - since you've stripped all the class names and just gone to AClass etc, it's really hard to actually understand the logic of your application.
http://en.wikipedia.org/wiki/Dependency_inversion_principle
Create an interface. Without knowing your actual classes it's impossible to give it a meaningful name, but you do this:
interface DInterface { }
class AClass implements DInterface { }
class BClass implements DInterface { }
class CClass
{
public function something(DInterface $class) { }
}
You can then define stuff inside DInterface that should be in both AClass and BClass, ie. if CClass::something expects to be able to call $class->all() then you can define public function all(); in DInterface, and PHP will error out if either AClass or BClass doesn't implement it.
I'm wondering how to force sub classes to implement a given interface method.
Let's say I have the following classes :
interface Serializable
{
public function __toString();
}
abstract class Tag // Any HTML or XML tag or whatever like <div>, <p>, <chucknorris>, etc
{
protected $attributes = array();
public function __get($memberName)
{
return $this->attributes[$member];
}
public function __set($memberName, $value)
{
$this->attributes[$memberName] = $value;
}
public function __construct() { }
public function __destruct() { }
}
I would like to force any sub class of "Tag" to implement the "Serializable" interface. For example, if i a "Paragraph" class, it would look this way :
class Paragraph extends Tag implements View
{
public function __toString()
{
print '<p';
foreach($this->attributes as $attribute => $value)
print ' '.$attribute.'="'.$value.'"';
print '>';
// Displaying children if any (not handled in this code sample).
print '</p>';
}
}
How can i force a developer to make his "Paragraph" class implement the methods from the interface "Serializable"?
Thanks for taking the time to read.
Just have the abstract class implement the interface:
interface RequiredInterface
{
public function getName();
}
abstract class BaseClass implements RequiredInterface
{
}
class MyClass extends BaseClass
{
}
Running this code will result in the error:
Fatal error: Class MyClass contains 1 abstract method and must
therefore be declared abstract or implement the remaining methods
(RequiredInterface::getName)
This requires the developer to code the methods of RequiredInterface.
PHP code example:
class Foo {
public function sneeze() { echo 'achoooo'; }
}
abstract class Bar extends Foo {
public abstract function hiccup();
}
class Baz extends Bar {
public function hiccup() { echo 'hiccup!'; }
}
$baz = new Baz();
$baz->sneeze();
$baz->hiccup();
It is possible for an abstract class to extend Serializable, as abstract classes do not need to be base classes
This adds a __constructor to your class Paragraph which checks to see if Serializable is implemented.
class Paragraph extends Tag implements View
{
public function __construct(){
if(!class_implements('Serializable')){
throw new error; // set your error here..
}
}
public function __toString()
{
print '<p';
foreach($this->attributes as $attribute => $value)
print ' '.$attribute.'="'.$value.'"';
print '>';
// Displaying children if any (not handled in this code sample).
print '</p>';
}
}
I have created a class here that contains an abstract method , it always return this errors to me even though that method is declared abstract. Here's my whole code
<?php
interface Validator{
public function isValid($input);
public function getErrors();
}
class Validator_AbstractValidator
{
protected $_errors = array();
abstract public function isValid($input);
public function getErrors()
{
return $this->_errors;
}
protected function _addError($message){
$this_errors[] = $message;
}
}
class Validator_MinimumLength extends Validator_AbstractValidator
{
protected $_minLength;
public function __construct($minLength)
{
$this_minLength = $minLength;
}
public function isValid($input){
if (strlen($input) > $this->_minLength) {
return true;
} else {
$this->_addError("Input must be at least {$this_minLength}");
return false;
}
}
}
class Validator_NoSpaces extends Validator_AbstractValidator{
public function isValid($input) {
if (preg_match('/\s/', $input)){
$this->_addError("Spaces are not allowed");
return false;
}
return true;
}
}
interface Form_ElementInterface extends Validator{ }
class Form_Element_AbstractElement extends Validator_AbstractValidator implements Form_ElementInterface
{
protected $_validators = array();
public function addValidator(Validator $validator)
{
$this->_validators[] = $validator;
}
public function getValidators()
{
return $this->_validators;
}
protected function _addErrors(array $errors)
{
foreach ($errors as $error) {
$this->_addError($error);
}
}
public function hasErrors()
{
return (count($this->getErrors()) !== 0);
}
public function isValid($input)
{
foreach ($this->_validators as $validator) {
if (!$validator->isValid($input)) {
$this->_addErrors($validator->getErrors());
}
}
return !$this->hadErrors();
}
}
class Form_Element extends Form_Element_AbstractElement
{
public function __construct($value,$minLength = 8)
{
$this->addValidator(new Validator_NoSpaces($value));
$this->addValidator(new Validator_MinimumLength($minLength));
//...
}
}
The error is this Fatal error: Class Validator_AbstractValidator contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Validator_AbstractValidator::isValid) in C:\xampp\htdocs\beatbeast\includes\Db\Validator.php on line 21
but line 21 only contains this
}
Is there anything I've missed?
Even though I added the keyward abstract in Class Validator_AbstractValidator class I encountered this problem
Fatal error: Can't inherit abstract function Validator::isValid() (previously declared abstract in Validator_AbstractValidator) in C:\xampp\htdocs\beatbeast\includes\Db\Validator.php on line 57
but once again line 57 only contains this
{
PHP 5 introduces abstract classes and methods. It is not allowed to
create an instance of a class that has been defined as abstract. Any
class that contains at least one abstract method must also be
abstract. Methods defined as abstract simply declare the method's
signature they cannot define the implementation.
if a class is not declared as abstract, it cannot contain any abstract methods.
abstract class Validator_AbstractValidator
{
//...
}
Update:
class Form_Element_AbstractElement extends Validator_AbstractValidator implements Form_ElementInterface
You are extending an abstract class (Validator_AbstractValidator) and also implementing the interface Validator all together, that is not possible.
Extend the class and implement the interface individually and implement the interface Validator in a separate class, not in Validator_AbstractValidator because it's an abstract class you've already declared before.
Implementing of interface Validator:
class from_validator_interface implement Validator{
// you have to implement all the methods declared in the interface, in your case
// `isValid` and `getErrors` both methods have to be implemented
}
Extending of abstract class Validator_AbstractValidator:
class someClass extends Validator_AbstractValidator{
// you don't need to implement all the abstract methods like interface
}
Some usefull links: php.net, other and more.
Your code works fine for me once I add abstract to Validator_AbstractValidator, which you say you already did.
abstract class Validator_AbstractValidator
{ ...
I had the same error with the following code :
Interface Fruit
{
public function getCalories() ;
public function countSeeds() ;
}
Interface Vegetable
{
public function getVitamins() ;
public function getCalories() ;
}
class Tomato implements Fruit, Vegetable
{
...
}
Here was my solution, I added a third parent interface.
Interface Food
{
public function getCalories() ;
}
Interface Fruit extends Food
{
public function countSeeds() ;
}
Interface Vegetable extends Food
{
public function getVitamins() ;
}
class Tomato implements Fruit, Vegetable
{
...
}