Liskov Substitution & DocBlock Inheritance
TypePHP respects the Liskov Substitution Principle (LSP). Child classes, implemented interfaces, traits, and abstract methods automatically inherit PHPDoc type contracts without requiring you to duplicate docblock annotations.
Abstract Method & Interface Contract Inheritance
When a child class implements an interface or extends an abstract parent class, it inherits all @param and @return contracts declared on the parent methods:
<?php
declare(strict_types=1);
namespace App\Services;
interface UserRepositoryInterface
{
/**
* Interface Contract
*
* @param positive-int $id
* @return array{id: positive-int, username: non-empty-string}
*/
public function findUser(int $id): array;
}
class UserRepository implements UserRepositoryInterface
{
// No docblock declared here! Inherits contract from UserRepositoryInterface.
public function findUser(int $id): array
{
return ['id' => $id, 'username' => 'Alice'];
}
}
$repo = new UserRepository();
// Valid Call
$repo->findUser(42);
// Invalid Call ($id is negative)
$repo->findUser(-5);
// Throws: TypeError: UserRepository::findUser(): Argument $id must be of type positive-intDeep Multi-Level Inheritance Chains
TypePHP recursively traverses inheritance trees down to any depth across classes, interfaces, and traits:
4-Level Class Inheritance (Level 1 $\rightarrow$ Level 2 $\rightarrow$ Level 3 $\rightarrow$ Level 4)
abstract class DeepLevel1
{
/**
* @param positive-int $id
* @return non-empty-string
*/
abstract public function process(int $id): string;
}
abstract class DeepLevel2 extends DeepLevel1 {}
abstract class DeepLevel3 extends DeepLevel2 {}
class DeepLevel4Executor extends DeepLevel3
{
// Level 4 concrete class with NO docblock! Inherits from Level 1 root.
public function process(int $id): string
{
return "item_{$id}";
}
}
$executor = new DeepLevel4Executor();
// $id = -50 violates Level 1 abstract parent's @param positive-int
$executor->process(-50);
// Throws: TypeError: DeepLevel4Executor::process(): Argument $id must be of type positive-intDeep Interface Chains (RootInterface $\leftarrow$ MidInterface $\leftarrow$ ChildInterface)
interface RootInterface
{
/**
* @param positive-int $code
*/
public function execute(int $code): bool;
}
interface MidInterface extends RootInterface {}
interface ChildInterface extends MidInterface {}
class InterfaceExecutor implements ChildInterface
{
// Implementation with NO docblock! Inherits from RootInterface.
public function execute(int $code): bool
{
return true;
}
}
$executor = new InterfaceExecutor();
// $code = -10 violates RootInterface's @param positive-int
$executor->execute(-10);
// Throws: TypeError: InterfaceExecutor::execute(): Argument $code must be of type positive-intPHP 8.4 Interface Property & Hook Inheritance
In PHP 8.4, interfaces can declare property hooks ({ get; set; }). Implementing classes inherit property @var contracts directly from the interface:
interface UserInterface
{
/**
* @var positive-int
*/
public int $id { get; }
/**
* @var non-empty-string
*/
public string $username { get; set; }
}
class User implements UserInterface
{
// Inherits @var positive-int from UserInterface
public int $id {
get => $this->_id;
}
public int $_id = 10;
// Inherits @var non-empty-string from UserInterface
public string $username {
get => $this->_username;
set => $this->_username = trim($value);
}
public string $_username = 'Alice';
}
$user = new User();
// Invalid Read ($user->_id = -5 violates interface's inherited @var positive-int)
$user->_id = -5;
$value = $user->id;
// Throws: TypeError: Property User::$id must be of type positive-int
// Invalid Write ($username = '' violates interface's inherited @var non-empty-string)
$user->username = '';
// Throws: TypeError: Property User::$username must be of type non-empty-stringTrait Instance & Static Property Inheritance
Instance and static properties declared in Traits inherit their @var docblock contracts when used by a class:
trait IdentifiableTrait
{
/**
* @var positive-int
*/
public int $traitId = 10;
/**
* @var non-empty-string
*/
public static string $traitVersion = '1.0';
public function setTraitId(int $val): void
{
$this->traitId = $val;
}
public static function setTraitVersion(string $val): void
{
self::$traitVersion = $val;
}
}
class AppModel
{
use IdentifiableTrait;
}
$model = new AppModel();
// $val = -50 violates Trait's @var positive-int
$model->setTraitId(-50);
// Throws: TypeError: Property AppModel::$traitId must be of type positive-int
// $val = '' violates Trait's @var non-empty-string
AppModel::setTraitVersion('');
// Throws: TypeError: Property AppModel::$traitVersion must be of type non-empty-stringTrait Inheritance Across Parent-Child Classes
When a parent class uses a Trait (ParentClass uses LoggerTrait), any child class extending the parent (ChildClass extends ParentClass) automatically inherits all @param, @return, and @var contracts declared on the parent's Trait:
trait LoggerTrait
{
/**
* @param positive-int $level
* @return non-empty-string
*/
public function logMessage(int $level, string $msg): string
{
return "log_{$level}_{$msg}";
}
}
class ParentService
{
use LoggerTrait; // Parent class uses trait
}
class ChildService extends ParentService
{
// Child class inherits logMessage() without declaring a docblock
}
$child = new ChildService();
// Valid Call
$child->logMessage(10, 'boot');
// Invalid Call ($level = -50 violates inherited Trait's @param positive-int)
$child->logMessage(-50, 'boot');
// Throws: TypeError: ChildService::logMessage(): Argument $level must be of type positive-intPartial Parameter Overriding (Gap-Filling)
If a child class overrides a method and provides a docblock for only some parameters, TypePHP fills in the missing parameter contracts from the parent class or interface:
class BaseService
{
/**
* Parent defines contracts for $id and $name
*
* @param positive-int $id
* @param non-empty-string $name
*/
public function update(int $id, string $name): bool
{
return true;
}
}
class ChildService extends BaseService
{
/**
* Child overrides ONLY $name to restrict allowed string literals!
*
* @param 'Alice'|'Bob' $name
*/
public function update(int $id, string $name): bool
{
return true;
}
}
$service = new ChildService();
// Valid Call
$service->update(10, 'Alice');
// Invalid $id (-5 violates parent's inherited @param positive-int)
$service->update(-5, 'Alice');
// Throws: TypeError: ChildService::update(): Argument $id must be of type positive-int
// Invalid $name ('Charlie' violates child's local @param 'Alice'|'Bob')
$service->update(10, 'Charlie');
// Throws: TypeError: ChildService::update(): Argument $name must be of type ('Alice' | 'Bob')Parameter Renaming ($id $\rightarrow$ $userId$)
PHP permits child classes to rename parameters when implementing an interface or extending a class. TypePHP maps inherited parameter contracts by index position (0, 1, 2...) rather than parameter name:
interface UserApiInterface
{
/**
* Interface uses parameter name $id
*
* @param positive-int $id
*/
public function find(int $id): bool;
}
class UserApi implements UserApiInterface
{
// Child renames parameter $id to $userId
public function find(int $userId): bool
{
return true;
}
}
$api = new UserApi();
// $userId = -50 is checked at index 0 against interface's @param positive-int $id!
$api->find(-50);
// Throws: TypeError: UserApi::find(): Argument $userId must be of type positive-intTrait & Interface Contract Fusion
When a class implements an Interface and fulfills its methods by using a Trait:
interface ExecutorInterface
{
/**
* @param positive-int $code
* @return non-empty-string
*/
public function execute(int $code): string;
}
trait ExecutorTrait
{
// Trait method fulfills the Interface with NO docblock
public function execute(int $code): string
{
return "code_{$code}";
}
}
class AppExecutor implements ExecutorInterface
{
use ExecutorTrait; // Trait method fulfills Interface contract
}
$app = new AppExecutor();
// $code = -5 violates ExecutorInterface's @param positive-int
$app->execute(-5);
// Throws: TypeError: AppExecutor::execute(): Argument $code must be of type positive-intIn-Memory Inheritance Caching Performance
To ensure that resolving complex inheritance chains introduces zero perceptible latency, TypePHP uses a 3-tier in-memory static caching architecture:
- End-Result Contract Cache (
ContractParser::$cache): Caches the fully resolved parameter, return, template, and alias metadata per method string (e.g."UserRepository::find"). - Reflection Hierarchy Cache (
HierarchyResolver): Caches the class, parent, interface, and trait Reflection tree ([Child, Parent, GrandParent, Interface1, Interface2]). If a class has 20 methods, its inheritance tree is inspected only once. - Property Contract Cache (
ContractParser::$propertyCache): Caches resolved property@vartypes ("UserProfile::$id").
How It Executes at Runtime
When you call $userRepo->find(42) 1,000 times in a loop:
- Invocation #1: TypePHP builds the
UserRepositoryinheritance tree, parses the docblocks, merges parent gaps, and caches the resolved contract in static RAM. - Invocations #2 through #1,000: TypePHP fetches the pre-resolved contract directly from static RAM in O(1) constant nanoseconds—zero Reflection traversal occurs!
Vendor DocBlock Isolation
TypePHP protects your application from third-party vendor docblock bugs using Vendor Isolation:
- If a parent class or interface is located inside an excluded folder (such as
/vendor/), TypePHP ignores its inherited docblocks. - This prevents third-party package docblock errors or outdated annotations from causing unexpected
TypeErrorexceptions in your application code.