You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/advanced/liskov-and-inheritance.md
+72-18Lines changed: 72 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -205,7 +205,9 @@ $model->setTraitId(-50);
205
205
AppModel::setTraitVersion('');
206
206
// Throws: TypeError: Property AppModel::$traitVersion must be of type non-empty-string
207
207
```
208
+
208
209
---
210
+
209
211
## Trait Inheritance Across Parent-Child Classes
210
212
211
213
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:
When a class uses a Trait and renames a method using PHP's trait `as` alias syntax, TypePHP inspects trait alias mappings and automatically inherits the original Trait method's DocBlock contracts onto the aliased method:
253
+
254
+
```php
255
+
trait LoggerTrait
256
+
{
257
+
/**
258
+
* @param positive-int $level
259
+
* @param non-empty-string $message
260
+
*/
261
+
public function logEvent(int $level, string $message): bool
262
+
{
263
+
return true;
264
+
}
265
+
}
266
+
267
+
class AuditService
268
+
{
269
+
use LoggerTrait {
270
+
logEvent as recordAuditLog; // Aliases method from trait!
## Parameter Renaming ($id → $userId) & Position Shifts
334
+
## Parameter Renaming ($id → $userId) & Position Shift Disambiguation
296
335
297
-
When a child classor attribute constructor overrides a parent method, parameter positions or parameter names may shift. TypePHP resolves parameter contract inheritance using **Name-First Resolution**:
336
+
When a child class, constructor, or trait implementation overrides an ancestor method, parameter positions may shift when new parameters are inserted, or parameter names may be renamed.
298
337
299
-
1.**Name Matching:** If a parameter name in the child method matches a parameter name in the parent class (e.g. `$api`), the parent's contract is inherited by that parameter regardless of its position index in the child.
300
-
2.**Position Fallback:** If a parameter is renamed in the child class (e.g., `$id` $\rightarrow$ `$userId`), TypePHP falls back to matching by position index.
338
+
TypePHP resolves parameter contract inheritance using **3-Tier Name & Position Disambiguation**:
339
+
340
+
1.**Name-First Matching:** If a parameter name in the child method matches a parameter name in the parent class (e.g. `$container`), the parent's contract is mapped to that parameter regardless of its position index in the child.
341
+
2.**Position Fallback on Renamed Parameters:** If a parameter is renamed in the child class (e.g., `$id` $\rightarrow$ `$userId`), TypePHP maps the contract using its position index.
342
+
3.**Candidate Disambiguation (Shift Protection):** If a child class inserts a new parameter at index 0 (shifting all subsequent parameters down), TypePHP **verifies that the candidate child parameter does not already exist in the parent under its own name**. This prevents parent parameter contracts from accidentally mis-mapping onto shifted child parameters!
301
343
302
344
```php
303
-
class BaseField
345
+
class BaseRegistry
304
346
{
305
347
/**
306
-
* Parent constructor has $api at position #1
348
+
* Parent constructor has 3 params:
349
+
* Index 0: $container
350
+
* Index 1: $definitions
351
+
* Index 2: $repositoryMap
307
352
*
308
-
* @param string $type
309
-
* @param bool|array{admin-api: bool} $api
353
+
* @param array<string,string> $definitions
354
+
* @param array<string,string> $repositoryMap
310
355
*/
311
-
public function __construct(string $type, bool|array $api = false) {}
356
+
public function __construct(
357
+
ContainerInterface $container,
358
+
array $definitions,
359
+
array $repositoryMap
360
+
) {}
312
361
}
313
362
314
-
class OneToManyRelation extends BaseField
363
+
class SalesChannelRegistry extends BaseRegistry
315
364
{
316
365
/**
317
-
* Child inserts $entity, $ref, $onDelete BEFORE $api (position shift!)
366
+
* Child inserts $prefix at Index 0 (shifting $container to Index 1),
367
+
* and renames $definitions -> $definitionMap at Index 2!
> **Execution Order Note:** Native PHP type hints (e.g., `int $id`, `string $username`) are evaluated by PHP's C-engine *before* function execution begins. TypePHP's extended PHPDoc contracts (e.g., `positive-int`, `non-empty-string`) execute at the very start of the function/method body. If a native type hint fails, PHP throws its native `TypeError` before TypePHP's guard rails run.
37
37
38
38
---
39
+
39
40
## PHP 8.0+ Named Arguments
40
41
41
42
TypePHP natively supports PHP 8.0+ Named Arguments. Because parameter contracts are mapped by parameter name rather than argument position index, you can pass named arguments in any order, and TypePHP will accurately validate each parameter:
// Throws: TypeError: deleteUsers(): Argument $ids[2] must be of type positive-int
211
-
```
212
-
213
-
---
214
-
215
194
## Fluent `$this` Identity Returns
216
195
217
196
For fluent builder or service classes annotated with `@return $this`, TypePHP verifies strict object identity (`$result === $this`), preventing accidental instantiation of new instances:
@@ -247,6 +226,96 @@ $builder->cloneSelf();
247
226
248
227
---
249
228
229
+
## Late Static Binding Return Contracts (`@return static`)
230
+
231
+
When a parent class method (static factory method or fluent instance method) is annotated with `@return static`, TypePHP enforces **Late Static Binding** at runtime.
232
+
233
+
It dynamically verifies that the returned object is an instance of the **actual calling class** (`UserEntityFactory`), strictly rejecting parent instances (`BaseEntityFactory`), sibling instances (`AdminEntityFactory`), or generic objects (`stdClass`):
234
+
235
+
```php
236
+
abstract class BaseEntityFactory
237
+
{
238
+
/**
239
+
* @return static
240
+
*/
241
+
public static function create(): static
242
+
{
243
+
return new static();
244
+
}
245
+
246
+
/**
247
+
* @return static
248
+
*/
249
+
public static function createSibling(): object
250
+
{
251
+
return new AdminEntityFactory(); // Invalid: Returns sibling instead of calling class!
252
+
}
253
+
}
254
+
255
+
class UserEntityFactory extends BaseEntityFactory {}
256
+
class AdminEntityFactory extends BaseEntityFactory {}
257
+
258
+
// Valid: Returns UserEntityFactory instance matching the late-static calling class
259
+
$user = UserEntityFactory::create();
260
+
261
+
// Invalid: UserEntityFactory called, but AdminEntityFactory was returned!
262
+
UserEntityFactory::createSibling();
263
+
// Throws: TypeError: UserEntityFactory::createSibling(): Return value must be of type App\UserEntityFactory, App\AdminEntityFactory returned
264
+
```
265
+
266
+
### Late Static Binding with Generics (`static<T>`)
267
+
268
+
Late static binding seamlessly integrates with TypePHP's Reified Generics engine. A static factory can return a specialized generic instance of the late-static-bound calling class:
269
+
270
+
```php
271
+
/**
272
+
* @template T
273
+
*/
274
+
abstract class BaseGenericFactory
275
+
{
276
+
/**
277
+
* @template TValue
278
+
* @param TValue $value
279
+
* @return static<TValue>
280
+
*/
281
+
public static function of(mixed $value): static
282
+
{
283
+
return new static($value);
284
+
}
285
+
}
286
+
287
+
class UserGenericFactory extends BaseGenericFactory {}
288
+
289
+
// 1. Returns UserGenericFactory instance
290
+
// 2. Binds generic template T = Dog in WeakMap memory!
291
+
$factory = UserGenericFactory::of(new Dog());
292
+
```
293
+
294
+
---
295
+
296
+
## Variadic Parameter Contracts
297
+
298
+
When a function or method accepts variadic arguments (`...$items`), TypePHP validates every element passed in the variadic argument list:
You can place your PHPDoc annotations **either above or below** native PHP attributes on properties, methods/functions. TypePHP's AST engine and PHP's Reflection API process both metadata channels independently without any syntax conflicts:
349
+
You can place your PHPDoc annotations **either above or below** native PHP attributes on properties, methods, or functions. TypePHP's AST engine and PHP's Reflection API process both metadata channels independently without any syntax conflicts:
0 commit comments