I have Entities and Repositories in my project. To simplify, I have
EntityInterfaceUserEntityBusinessEntityInterface:
interface Entity
{
/**
* @return EntityId
*/
public function getId();
}
Implementations
class UserEntity implements Entity
{
/**
* @return EntityId
*/
public function getId(){
//...do something here for return
return $userId;
}
}
and
class BusinessEntity implements Entity
{
/**
* @return EntityId
*/
public function getId(){
//...do something here for return
return $userId;
}
}
I would like to define a Repository base-functionality, like save, so my interface looks like:
interface Repository
{
/**
* @param Entity $entity
*
* @throws \InvalidArgumentException If argument is not match for the repository.
* @throws UnableToSaveException If repository can't save the Entity.
*
* @return Entity The saved entity
*/
public function save(Entity $entity);
}
Later, I have different interfaces for different type of Repositories, like UserRepository and BusinessRepository
interface BusinessRepository extends Repository
{
/**
* @param BusinessEntity $entity
*
* @throws \InvalidArgumentException If argument is not match for the repository.
* @throws UnableToSaveException If repository can't save the Entity.
*
* @return Entity The saved entity
*/
public function save(BusinessEntity $entity);
}
The above code fails, because Declaration must be compatible with Repository...
however BusinessEntity implements Entity, so it's compatible.
I have many type of entities, so If I can't type-hint, I always need to check, that the passed instance is instanceof what I need. It's stupid.
The following code fails again:
class BusinessRepository implements Repository
{
public function save(BusinessEntity $entity)
{
//this will fail, however BusinessEntity is an Entity
}
}
In general, method parameters have to be contravariant with respect to an inheritance hierarchy or invariant. This means that indeed BusinessEntity would not be "compatible" with Entity when used as a type for a method parameter.
Think of it from a "contract" point of view. Your interface Repository promises that its method save can handle arguments of type Entity. Subtypes inheriting from Repository should be bound to this introduced contract (because otherwise, what sense would it make to define types in the first place, if you cannot be sure what they promises to be able to do?).
Now, if a subtype all of a sudden only accepts more special types, like BusinessEntity, but no longer Entity, the contract's broken. You cannot use BusinessRepository as Repository any more, because you cannot call save with an Entity.
This is counterintuitive at first, but have a look at this: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_argument_type
Notice the inheritance arrow in the image.
What's to do? Get rid of the idea of inheritance being the holy grail in object oriented programming. Most of the time, it is not, and introduces all kinds of nasty coupling. Favor composition over inheritance, for example. Have a look at Parameter type covariance in specializations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With