I thought I understand the LSP, but it seems I'm totally wrong. I have the following classes:
class PrimitiveValue {
}
class StringValue extends PrimitiveValue {
}
class A {
public function foo(StringValue $value) {
}
}
class B extends A {
public function foo(PrimitiveValue $value) {
}
}
Note that the base class A accepts the subclass StringValue as a parameter type, and the subclass B accepts the parent class PrimitiveValue. I'm saying this because a similar question, just with reversed types, gets being asked everywhere.
According to my understanding, the method foo() in class B accepts anything that the parent method accepts, plus more, since it accepts the base type PrimitiveValue. Thus, any caller who just sees class A passes values that can be handled by B, not violating the LSP. Callers who know that it is a B can make stronger assumptions and are free to pass other PrimitiveValue subtypes.
Yet when I execute the code, I get the error:
Strict (2048): Declaration of B::foo() should be compatible with A::foo(StringValue $value) [APP/Controller/TestLocalController.php, line 17]
What am I getting wrong? I think most helpful would be an example of how a value can be passed that violates the assumptions the code makes about that value. Assuming that it is clear what I'm trying to achieve, please also add code on how to do it correctly.
What am I getting wrong? I think most helpful would be an example of how a value can be passed that violates the assumptions the code makes about that value.
You didn't misunderstand LSP, allowing a bigger type in a subclassed method doesn't violate the principle; in fact, what you've described is known as contravariant method argument types, something which is not supported in PHP either by design or implementation difficulties.
Personally, my only reservation about this approach is that handling of the weaker type within your subclass is hidden by the superclass.
Assuming that it is clear what I'm trying to achieve, please also add code on how to do it correctly.
Method argument types in PHP are invariant, i.e. the type for each argument (if supplied in the parent) must match exactly when overriding a method. While this behaviour has been discussed in the past and brought up again recently with the introduction of return type hinting, it's not guaranteed to be available even in the next major version.
To overcome this, you're currently forced to making both primitive and derived types implement the same interface and then use that in both parent and child classes, as described by this answer.