I am trying to understanding a little best the Class Invariances used by Liskov Principle.
I know that some languages like D have native support to invariant, but, using asserts in PHP I've tried combining magic methods and assert:
<?php
class Person {
protected string $name;
protected string $nickName;
protected function testContract(){
assert(($this->name != $this->nickName));
}
public function __construct(string $name, string $nickName){
$this->name = $name;
$this->nickName = $nickName;
}
public function __set($name, $value){
$this->testContract();
$this->$name = $value;
}
public function __get($name){
$this->testContract();
return $this->$name;
}
}
class GoodPerson extends Person {
public function getFullName(){
return $this->name." ".$this->nickName. "!!!";
}
}
class BadPerson extends Person {
protected function testContract(){
assert(($this->name != ""));
}
}
$gp = new GoodPerson("João", "Joãozinho");
echo $gp->nickName;
echo $gp->getFullName();
$bp = new BadPerson("João", "João");
echo $bp->nickName;
Can I use assert to create a Contract?
No
From PHP Documentation
Is BadPerson a valid example to Liskov's Class Invariance violation on inheritance?
Yes
Preconditions cannot be strengthened in a subtype.
But your code doesn't make any sense
First your testContract
method will only be invoked if you try to set or get a dynamic property and it will check for the parameters you passed through the constructor
public function __set($name, $value){
$this->testContract();
$this->$name = $value;
}
Here you basically test the constructor parameters but in the magic method (__set
)
So in order to make that check works you need to invoke the __set
like this
$gp = new BadPerson("João", "Joãozinho");
$gp->name = ''; // This line here invokes __set magic method
So what you really need to do is get rid of testContract
and put the check inside the base class constructor. Why? because your properties are protected
so the only opportunity that clients have for setting them is through the constructor
public function __construct(string $name, string $nickName){
if ($name != "" && $name != $nickName)
throw new Exception('Name must not be empty and must not equal Nickname');
$this->name = $name;
$this->nickName = $nickName;
}
Is GoodPerson a valid example to Liskov's Classe Invariance?
Yes