Type hint for descendant classes - php

From the docs page http://php.net/manual/en/language.oop5.typehinting.php
If class or interface is specified as type hint then all its children or implementations are allowed too.
But:
class PimpleChild extends Pimple {
//...
}
interface Pimple_Config {
public function configure(Pimple $container);
}
class PimpleConfigurer_Factories implements Pimple_Config {
public function configure(PimpleChild $container) {
//...
}
}
returns fatal error. Why?

If I am not mistaken you get this error:
Declaration of PimpleConfigurer_Factories::configure() must be compatible with Pimple_Config::configure(Pimple $container) ...
Which means: If you define a method in a super class or in an interface, all sub classes (or classes implementing the interface) must use exactly this definition. You cannot use another type here.
As for your quote from the documentation:
If class or interface is specified as type hint then all its children or implementations are allowed too.
This only means that you can pass a variable which is of a certain type or all its children.
For example: Say you have the following classes:
class Car {
protected $hp = 50;
public function getHp() { return $this->hp; }
}
class SuperCar extends Car {
protected $hp = 700;
}
And a function (or method, no difference there) with type hint:
function showHorsePower(Car $car) {
echo $car->getHp();
}
You can now pass all objects of type Car and all its sub classes (here SuperCar) to this function, like:
showHorsePower(new Car());
showHorsePower(new SuperCar());

From the same typehinting section you linked to:
The class implementing the interface must use the exact same method signatures as are defined in the interface. Not doing so will result in a fatal error.
For the method signatures to be the same, they must contain the exact same typehints. And also relevant because it is similar...
From the OOP Basics - extends section of the manual:
When overriding methods, the parameter signature should remain the same or PHP will generate an E_STRICT level error. This does not apply to the constructor, which allows overriding with different parameters.

Related

Fatal error: Declaration of .. must be compatible with .. PHP (parameter required: extended class of specification)

I'm working on the next version of my language-constraint reconciliation system. I started with this (the abstract function declaration seems to be the problem):
namespace AAABIT;
abstract class LangPrefSet{
private $LangPrefs;
public function __construct(){$this->LangPrefs=array();}
public function add(LangPref $langpref){$this->LangPrefs[]=$langpref;}
public function langPrefs(){return $this->LangPrefs;}
abstract public function reconcile(LangPrefSet $other);//←Seems to be throwing an error… We don't strictly need this line, but this is a little concerning…
protected static function reconcile_LangPrefSets(LangPrefSet_ForUser $UserLangPrefSet,LangPrefSet_Resource $RsrcLangPrefSet,$maxOptions){//…
}
}
//Following classes are necessary because language similarity is a one-way mapping. Just because resources in Lang A (e.g. Russian) are likely to be readily understandable for speakers/readers of Lang B (e.g. Ukrainian), does not mean that the resources in Lang B (e.g. Ukrainian) are equally intelligible for speakers/readers of Lang A (e.g. Russian)!
class LangPrefSet_For_User extends LangPrefSet{public function reconcile(LangPrefSet_Resource $RsrcLangPrefSet){return self::reconcile_LangPrefSets(self,$RsrcLangPrefSet);}}
class LangPrefSet_Resource extends LangPrefSet{public function reconcile(LangPrefSet_For_User $UserLangPrefSet){return self::reconcile_LangPrefSets($UserLangPrefSet,self);}}
I thought this would work because LangPrefSet_Resource conforms to LangPrefSet; but PHP found this objectionable, throwing the aforementioned error. I thought I might have better luck with an Interface… So I did this:
interface LangPrefSet_Reconcilable{
public function reconcile(LangPrefSet_Reconcilable $other);
}
Then I made the two classes extending LangPrefSet, implements LangPrefSet_Reconcilable, and commented out the abstract function declaration (after first trying to make that require an LangPrefSet_Reconcilable interface-typed parameter, also which didn't work) — the results are:
Fatal error: Declaration of AAABIT\LangPrefSet_For_User::reconcile() must be compatible with AAABIT\LangPrefSet_Reconcilable::reconcile(AAABIT\LangPrefSet_Reconcilable $other)
— This is not a blocker issue for me, since I can just strip out the abstract function and interface and the system will work fine. However I'm concerned that I might not have understood interfaces/ abstract classes properly!
What is wrong with specifying that a class method overriding abstract function a(ObjB $b) or a similar interface specification, takes as a parameter ObjC $c, where ObjC extends ObjB?
Your parameter type must be the same in the interface(or abstract class) and where you are implementing the same. right now the interface is LangPrefSet_Reconcilable
and the implementation says LangPrefSet_Resource and LangPrefSet_For_User.
You can either use a 2nd interface like below, or use the same class.
interface LangPrefSetReconcilableDataInteface {
...
}
interface LangPrefSet_Reconcilable{
public function reconcile(LangPrefSetReconcilableDataInteface $other);
}
class LangPrefSet_Resource implements LangPrefSetReconcilableDataInteface {
...
}
class LangPrefSet_For_User implements LangPrefSetReconcilableDataInteface {
...
}
class LangPrefSet_For_User extends LangPrefSet
{
public function reconcile(LangPrefSetReconcilableDataInteface $RsrcLangPrefSet)
{
return self::reconcile_LangPrefSets(self,$RsrcLangPrefSet);
}
}
class LangPrefSet_Resource extends LangPrefSet
{
public function reconcile(LangPrefSetReconcilableDataInteface $UserLangPrefSet)
{
return self::reconcile_LangPrefSets($UserLangPrefSet,self);
}
}
The issue may to be that in the abstract class declaration, I am specifying that any LangPrefSet must be acceptable to the reconcile() object method:
abstract public function reconcile(LangPrefSet $other);
— Whereas in the actual method declaration, I am specifying in one case that only a LangPrefSet_Resource would be acceptable, and in the other case, that only a LangPrefSet_For_User would be acceptable.
The object method declarations are therefore incompatible with the abstract method declaration. It seems that the proper application of these interface/abstract method declaration techniques is not to broaden the range of permissible parameter type constraints in classes that implement the interface, since we may achieve this simply by declaring the input restrictions as they ought to be for those specific classes/methods; but rather, interface/abstract method declarations are meant to broaden the range of parameters that may actually be passed to a method that specifies these abstract parameter types.

PHP OOP Declaration of interface implementation compatibility

I have the following OOP structure:
<?php
interface AnimalInterface
{
public function getName();
}
class Dog implements AnimalInterface
{
public function getName() {
return 'dog';
}
public function makeFriends()
{
echo 'I have friends now :)';
}
}
class Cat implements AnimalInterface
{
public function getName() {
return 'cat';
}
public function hateFriends()
{
echo 'I cant make friends :(';
}
}
interface AnimalDoInterface
{
public function makeFriend(AnimalInterface $animal);
}
class DogFriend implements AnimalDoInterface
{
public function makeFriend(Dog $dog)
{
$dog->makeFriends();
}
}
class CatFriend implements AnimalDoInterface
{
public function makeFriend(Cat $cat)
{
$cat->hateFriends();
}
}
Now PHP's manual on Object Interfaces says:
The class implementing the interface must use the exact same method signatures as are defined in the interface. Not doing so will result in a fatal error.
Why is this the case? Am I misunderstanding interfaces completely? Surely I should be able to declare AnimalDoInterface::makeFriend with anything that is the interface or an implementation of that interface? In this case it should technically be compatible as Cat implements AnimalInterface, which is what it's expecting.
Regardless of whether I am getting my OOP wrong, is there a way to implement this in PHP?
So it seems I wasn't clear enough, my bad for that. However, basically what I'm trying to achieve is to have the implementations of AnimalDoInterface to be more restrictive than it's interface says. So in this case, I'd like DogFriend::makeFriend to only allow the Dog class as it's argument, which in my mind should be acceptable as it implements the AnimalInterface, and the CatFriend to allow a Cat class, which again, same thing.
EDIT: fixed the classes, also added what I'm trying to achieve.
EDIT 2:
So at the moment, the way I'd have to implement it is as following:
class DogFriend implements AnimalDoInterface
{
public function makeFriend(AnimalInterface $dog)
{
if(!($dog instanceof Dog)) {
throw new \Exception('$dog must be of Dog type');
}
$dog->makeFriends();
}
}
class CatFriend implements AnimalDoInterface
{
public function makeFriend(AnimalInterface $cat)
{
if(!($dog instanceof Cat)) {
throw new \Exception('$dog must be of Cat type');
}
$cat->hateFriends();
}
}
I'd like to have to avoid this extra check for the class type.
An interface's only job is to enforce the fact that two objects behave in an identical way, regardless of how they implement that behaviour. It is a contract stating that two objects are interchangeable for certain specific purposes.
(Edit: This part of the code has been corrected, but serves as a good introduction.) The interface AnimalInterface defines the behaviour (function) getAnimalName(), and any class claiming to implement that interface must implement that behaviour. class Dog is declared with implements AnimalInterface, but doesn't implement the required behaviour - you can't call getAnimalName() on instances of Dog. So we already have a fatal error, as we have not met the "contract" defined by the interface.
Fixing that and proceeding, you then have an interface AnimalDoInterface which has the defined behaviour (function) of makeFriend(AnimalInterface $animal) - meaning, you can pass any object which implements AnimalInterface to the makeFriend method of any object which implements AnimalDoInterface.
But you then define class DogFriend with a more restrictive behaviour - its version of makeFriend can only accept Dog objects; according to the interface it should also be able to accept Cat objects, which also implement AnimalInterface, so again, the "contract" of the interface is not met, and we will get a fatal error.
If we were to fix that, there is a different problem in your example: you have a call to $cat->hateFriends(); but if your argument was of type AnimalInterface or AnimalDoInterface, you would have no way to know that a hateFriends() function existed. PHP, being quite relaxed about such things, will let you try that and blow up at runtime if it turns out not to exist after all; stricter languages would only let you use functions that are guaranteed to exist, because they are declared in the interface.
To understand why you can't be more restrictive than the interface, imagine you don't know the class of a particular object, all you know is that it implements a particular interface.
If I know that object $a implements AnimalInterface, and object $b implements AnimalDoInterface, I can make the following assumptions, just by looking at the interfaces:
I can call $a->getName(); (because AnimalInterface has that in its contract)
I can call $b->makeFriend($a); (because AnimalDoInterface has in its contract that I can pass anything that implements AnimalInterface)
But with your code, if $a was a Cat, and $b was a DogFriend, my assumption #2 would fail. If the interface let this happen, it wouldn't be doing its job.
The reason all classes implementing an interface must have the same methods is so that you can call that method on the object regardless of which subtype is instantiated.
In this case, you have a logical inconsistency because two subtypes, Cat and Dog, have different methods. So you can't call makeFriends() on the object, because you don't know that the object has that method.
That's the point of using interfaces, so you can use different subtypes, but at the same time you can be sure of at least some common methods.
One way to handle this in your case is to make sure Cat implements the same method, but make the method throw an exception at runtime, indicating that it's not possible. This allows the interface to be satisfies at compile time (I know PHP doesn't have a compiler, but this is mimicking languages like Java that do the interface checking at compile time).
class Cat implements AnimalInterface
{
public function makeFriends()
{
throw new RuntimeException('I cant make friends :(');
}
}
A class which implements AnimalDoInterface must have a makeFriend method which takes any object which implements AnimalInterface. In your case, trying to declare
class DogFriend implements AnimalDoInterface {
public function makeFriend(Dog $foo) { }
}
will not accurately implement this, since it should always be safe to pass anything which implements AnimalInterface to the makeFriend method of anything which implements AnimalDoInterface.

PHP - override function with different number of parameters

I'm extending a class, but in some scenarios I'm overriding a method. Sometimes in 2 parameters, sometimes in 3, sometimes without parameters.
Unfortunately I'm getting a PHP warning.
My minimum verifiable example:
http://pastebin.com/6MqUX9Ui
<?php
class first {
public function something($param1) {
return 'first-'.$param1;
}
}
class second extends first {
public function something($param1, $param2) {
return 'second params=('.$param1.','.$param2.')';
}
}
// Strict standards: Declaration of second::something() should be compatible with that of first::something() in /home/szymon/webs/wildcard/www/source/public/override.php on line 13
$myClass = new Second();
var_dump( $myClass->something(123,456) );
I'm getting PHP error/warning/info:
How can I prevent errors like this?
you can redefine methods easily adding new arguments, it's only needs that the new arguments are optional (have a default value in your signature). See below:
class Parent
{
protected function test($var1) {
echo($var1);
}
}
class Child extends Parent
{
protected function test($var1, $var2 = null) {
echo($var1);
echo($var1);
}
}
For more detail, check out the link: http://php.net/manual/en/language.oop5.abstract.php
Another solution (a bit "dirtier") is to declare your methods with no argument at all, and in your methods to use the func_get_args() function to retrieve your arguments...
http://www.php.net/manual/en/function.func-get-args.php
As of PHP 8.1, there's a cool hack to override a class's method with extra number of required arguments. You should use the new new in initializers feature. But how?
We define a class having a constructor always throwing a ArgumentCountError, and make it the default value of every extra required parameter (an improved version of #jose.serapicos's answer). Simple and cool!
Now let's see it in action. First, we define RequiredParam:
final class RequiredParameter extends \ArgumentCountError
{
public function __construct()
{
// Nested hack
throw $this;
}
}
And then:
class Base
{
public function something(string $baseParam): string
{
return $baseParam;
}
}
class Derived extends Base
{
public function something(
string $baseParam,
string|RequiredParameter $extraParam = new RequiredParameter(),
): string {
return "$baseParam + $extraParam";
}
}
This way, no one can bypass the extra parameters, because RequiredParameter is declared as final. It works for interfaces as well.
How Good or Bad is This?
One advantage is that it's a little more flexible than setting default parameters as null, as you can pass the constructor of RequiredParameter an arbitrary list of parameters and probably build a custom error message.
Another advantage is that it's handled less manually, and thus being more safe. You may forget about handling a null value, but RequiredParameter class handles things for you.
One major disadvantage of this method is that it breaks the rules. First and foremost, you must ask yourself why you would need this, because it breaks polymorphism in most cases. Use it with caution.
However, there are valid use cases for this, like extending parent class's method with the same name (if you cannot modify the parent, otherwise I recommend you to use traits instead), and using the child class as standalone (i.e. without the help of parent class's type).
Another disadvantage is that it requires you to use union types for each parameter. While the following workaround is possible, but it requires you to create more classes, which may hurt understandability of your code, as well as having little impact on maintainability and performance (based on your conditions). BTW, no hack comes for free.
Eliminating the use of Union Type
You could extend from or implement RequiredParameter the compatible type of the actual parameter to be able to remove the need for union type:
class BaseRequiredParameter extends Base
{
public function __construct()
{
throw new \ArgumentCountError();
}
}
class Derived extends Base
{
public function something(
string $baseParam,
Base $extraParam = new BaseRequiredParameter()
): string {
return "$baseParam + {$extraParam->something()}";
}
}
It's also possible for strings, if you implement the Stringable interface (e.g. Throwable implements it by default). It doesn't work for some primitive types including bool, int, float, callable, array, etc., however, if you're interested, you're still able to use some alternatives like Closure or Traversable.
For making your life easier, you may want to define the constructor as a trait and use it (I'm aware of this answer, but in fact, this is a valid useful case for a constructor in a trait, at least IMO).
Your interface/abstract class or the most parent class, should cotantin the maximum number of params a method could recieve, you can declare them explicitely to NULL, so if they are not given, no error will occur i.e.
Class A{
public function smth($param1, $param2='', $param3='')
Class B extends A {
public function smth($param1, $param2, $param3='')
Class C extends B {
public function smth($param1, $param2, $param3);
In this case, using the method smth() as an object of 'A' you will be obligated to use only one param ($param1), but using the same method as object 'B' you will be oblgiated to use 2 params ($param1, $param2) and instanciating it from C you have to give all the params

Can you override interface methods with different, but "compatible", signatures?

Consider the following PHP interfaces:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(SuperItem $item);
// more methods here that "override" the Collection methods like "add()" does
}
I'm using PHPStorm, and when I do this, I get an error in the IDE that basically states the definition for add() in SuperCollection is not compatible with the definition in the interface it extends, Collection.
In one way, I can see this being a problem, as the signature of the method does not match the one it "overrides" exactly. However, I do feel that this would be compatible, as SuperItem extends Item, so I would view add(SuperItem) the same as add(Item).
I'm curious as to if this is supported by PHP (version 5.4 or above), and maybe the IDE has a bug that doesn't properly catch this.
No, I'm pretty sure PHP doesn't support this, in any version, and it would rather defeat the point of an interface.
The point of an interface is that it gives you a fixed contract with other code that references the same interface.
For example, consider a function like this:
function doSomething(Collection $loopMe) { ..... }
This function expects to receive an object that implements the Collection interface.
Within the function, the programmer would be able to write calls to methods that are defined in Collection, knowing that the object would implement those methods.
If you have an overridden interface like this, then you have a problem with this, because a SuperCollection object could be passed into the function. It would work because it also is a Collection object due to the inheritance. But then the code in the function could no longer be sure that it knows what the definition of the add() method is.
An interface is, by definition, a fixed contract. It is immutable.
As an alternative, you could consider using abstract classes instead of interfaces. This would allow you to override in non-Strict mode, although you'll still get errors if you use Strict Mode, for the same reasons.
As a workaround I'm using PHPDoc blocks in interfaces.
interface Collection {
/**
* #param Item $item
*/
public function add($item);
// more methods here
}
interface SuperCollection extends Collection {
/**
* #param SuperItem $item
*/
public function add($item);
// more methods here that "override" the Collection methods like "add()" does
}
This way, in case you are properly using interfaces, IDE should help you catch some errors. You can use similar technique to override return value types as well.
PHP 7.4, released November 2019 has improved type variance.
The code from the question remains invalid:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(SuperItem $item); // This will still be a compile error
// more methods here that "override" the Collection methods like "add()" does
}
Because the Collection interface guarantees that anything that implements it can accept any object of type Item as the parameter of add.
However, the following code is valid in PHP 7.4:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(SuperItem $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(Item $item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
In this case Collection guarantees that that it can accept any SuperItem. Since all SuperItems are Items, SuperCollection also makes this guarantee, while also guaranteeing that it can accept any other type of Item. This is known as a Contravariant method parameter type.
There is a limited form of type variance in earlier versions of PHP. Assuming other interfaces are as in the question, the SuperCollection may be defined as:
interface SuperCollection extends Collection {
public function add($item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
This can be interpreted as meaning any value whatsoever may be passed to the add method. That of course includes all Items, so this is still type safe, or it can be interpreted as meaning that an unspecified class of values, generally documented as mixed may be passed and the programmer needs to use other knowledge of exactly what can work with the function.
You cannot change the methods arguments.
http://php.net/manual/en/language.oop5.interfaces.php
Extending interface is not allowed to change method definitions. If your SuperItem is extending Item, it should be passing through classes implementing Collection interface without problems.
But based on what you really want to do, you can try:
Create interface with slightly different methods for SuperItem and implement that:
interface SuperCollection extends Collection {
public function addSuper(SuperItem $superItem);
}
Use decorator pattern to create almost same interface without extending:
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection {
public function add(SuperItem $item);
// more methods here that "override" the Collection methods like "add()" does
}
Then decorator (abstract) class, which will use this interface:
class BasicCollection implements Collection {
public function add(Item $item)
{
}
}
class DecoratingBasicCollection implements SuperCollection {
protected $collection;
public function __construct(Collection $collection)
{
$this->collection = $collection;
}
public function add(SuperItem $item)
{
$this->collection->add($item);
}
}
The problem is not in the IDE. In PHP you cannot override a method. And the compatibility is only in the opposite direction - you can safely expect an instance of the parent class and receive a subclass. But when you expect a subclass, you cannot be safe if you receive the parent class - the subclass may define methods that do not exist in the parent. But still, you can't override the method
When I have a method that I may need to overload (which PHP does not support), I make sure that one of the method arguments (usually the last) is an array. In this way I can pass whatever I need to. I can then test within the function for various array elements to tell me what routine in the method I need to perform, usually in a select/case.

Can parameter types be specialized in PHP

Say we've got the following two classes:
abstract class Foo {
public abstract function run(TypeA $object);
}
class Bar extends Foo {
public function run(TypeB $object) {
// Some code here
}
}
The class TypeB extends the class TypeA.
Trying to use this yields the following error message:
Declaration of Bar::run() must be compatible with that of Foo::run()
Is PHP really this broken when it comes to parameter types, or am I just missing the point here?
This answer is outdated since PHP 7.4 (partially since 7.2).
The behavior you describe is called covariance and is simply not supported in PHP. I don't know the internals but I might suspect that PHP's core does not evaluate the inheritance tree at all when applying the so called "type hint" checks.
By the way, PHP also doesn't support contravariance on those type-hints (a feature commonly support in other OOP languages) - most likely to the reason is suspected above. So this doesn't work either:
abstract class Foo {
public abstract function run(TypeB $object);
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
And finally some more info: http://www.php.net/~derick/meeting-notes.html#implement-inheritance-rules-for-type-hints
This seems pretty consistent with most OO principals. PHP isn't like .Net - it doesn't allow you to override class members. Any extension of Foo should slide into where Foo was previously being used, which means you can't loosen constraints.
The simple solution is obviously to remove the type constraint, but if Bar::run() needs a different argument type, then it's really a different function and should ideally have a different name.
If TypeA and TypeB have anything in common, move the common elements to a base class and use that as your argument constraint.
I think this is by design: It is the point of abstract definitions to define the underlying behaviour of its methods.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility.
One could always add the constraint in code:
public function run(TypeA $object) {
assert( is_a($object, "TypeB") );
You'll have to remember or document the specific type limitation then. The advantage is that it becomes purely a development tool, as asserts are typically turned off on production servers. (And really this is among the class of bugs to be found while developing, not randomly disrupt production.)
The code shown in the question is not going to compile in PHP. If it did class Bar would be failing to honour the gurantee made by it's parent Foo of being able to accept any instance of TypeA, and breaching the Liskov Substitution Principle.
Currently the opposite code won't compile either, but in the PHP 7.4 release, expected on November 28 2019, the similar code below will be valid, using the new contravariant arguments feature:
abstract class Foo {
public abstract function run(TypeB $object); // TypeB extends TypeA
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
All Bars are Foos, but not all Foos are Bars. All TypeBs are TypeAs but not all TypeAs are TypeBs. Any Foo will be able to accept any TypeB. Those Foos that are also Bars will also be able to accept the non-TypeB TypeAs.
PHP will also support covariant return types, which work in the opposite way.
Although you cannot use whole class-hierarchies as type-hinting. You can use the self and parent keywords to enforce something similar in certain situations.
quoting r dot wilczek at web-appz dot de from the PHP-manual comments:
<?php
interface Foo
{
public function baz(self $object);
}
class Bar implements Foo
{
public function baz(self $object)
{
//
}
}
?>
What has not been mentioned by now is that you can use 'parent' as a typehint too. Example with an interface:
<?php
interface Foo
{
public function baz(parent $object);
}
class Baz {}
class Bar extends Baz implements Foo
{
public function baz(parent $object)
{
//
}
}
?>
Bar::baz() will now accept any instance of Baz.
If Bar is not a heir of any class (no 'extends') PHP will raise a fatal error:
'Cannot access parent:: when current class scope has no parent'.

Categories