phpclassliskov-substitution-principleinvariance

Is this a valid example of Class invariances using php 7 asserts?


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;

Solution

  • 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