Skip to content

Commit 211d8f3

Browse files
authored
Internal improvements (#10)
* reorganize documentation sections * Refactor GenericValidator to handle invalid class syntax gracefully and improve test cases for unsupported type syntax * Add support for new types: uppercase-string, non-empty-uppercase-string, and array-key; update validators and tests accordingly * Add int-mask type checking support * Added runtime checking for offset array access * Add support for offset access types and integer bitmasks in documentation * fix name argument parsing mismatch cuasing false positive type-errors and document it.
1 parent a3e76a3 commit 211d8f3

41 files changed

Lines changed: 1500 additions & 165 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<h1 align="center">TypePHP</h1>
22

3+
<p align="center">
4+
<b>No transpilation. No build steps. No C-extensions.<br>
5+
Drop TypePHP into your existing codebase and let your DocBlocks scream when types fail.</b>
6+
</p>
7+
38
<p align="center">
49
<a href="https://github.com/typephp-php/typephp/actions"><img src="https://github.com/typephp-php/typephp/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a>
510
<a href="https://packagist.org/packages/typephp/typephp"><img src="https://img.shields.io/packagist/v/typephp/typephp.svg?style=flat&color=blue" alt="Latest Stable Version"></a>
@@ -11,7 +16,7 @@
1116

1217
------
1318

14-
TypePHP is the first pure-PHP library that transparently enforces extended PHPDoc type contracts (generics, array shapes, scalar refinements, and callables) at runtime during execution, without introducing any new syntax or requiring C-extensions.
19+
TypePHP is a transparent, pure-PHP runtime type checker. You don't have to refactor a single line of your codebase, setup complex build toolchains, or compile C-extensions and simply run your existing code, and TypePHP will enforce your extended PHPDoc contracts (generics, array shapes, `key-of`/`value-of` extractions, and scalar refinements) dynamically at runtime.
1520

1621

1722
**[Read the full TypePHP documentation »](https://typephp-php.github.io/typephp/)**

docs/.vitepress/config.mts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ export default defineConfig({
99
nav: [
1010
{ text: 'Home', link: '/' },
1111
{ text: 'Documentation', link: '/getting-started/installation' },
12-
{ text: 'Architecture', link: '/architecture/how-it-works' },
13-
{ text: 'CLI', link: '/production/cache-commands' },
12+
{ text: 'Generics', link: '/generics/generics-and-bounds' },
13+
{ text: 'CLI', link: '/getting-started/cli-commands' },
14+
{ text: 'FAQ', link: '/troubleshooting' },
1415
{ text: 'GitHub', link: 'https://github.com/typephp-php/typephp' }
1516
],
1617
sidebar: [
@@ -20,52 +21,57 @@ export default defineConfig({
2021
{ text: 'Installation', link: '/getting-started/installation' },
2122
{ text: 'Quick Start', link: '/getting-started/quick-start' },
2223
{ text: 'Configuration', link: '/getting-started/configuration' },
24+
{ text: 'CLI Commands', link: '/getting-started/cli-commands' },
2325
]
2426
},
2527
{
26-
text: 'Architecture',
27-
items: [
28-
{ text: 'How It Works', link: '/architecture/how-it-works' },
29-
]
30-
},
31-
{
32-
text: 'Core Concepts',
28+
text: 'Enforcement Boundaries',
3329
items: [
3430
{ text: 'Function Contracts', link: '/core-concepts/function-contracts' },
35-
{ text: 'Inline Variables', link: '/core-concepts/inline-variables' },
3631
{ text: 'Property Validation', link: '/core-concepts/property-validation' },
37-
{ text: 'Generics & Bounds', link: '/core-concepts/generics-and-bounds' },
38-
{ text: 'Type Aliases', link: '/core-concepts/type-aliases' },
32+
{ text: 'Inline Variables', link: '/core-concepts/inline-variables' },
3933
]
4034
},
4135
{
42-
text: 'Supported Types',
36+
text: 'Type Reference',
4337
items: [
4438
{ text: 'Primitives & Scalars', link: '/supported-types/primitives-and-scalars' },
4539
{ text: 'Arrays & Shapes', link: '/supported-types/arrays-and-shapes' },
4640
{ text: 'Callables & Closures', link: '/supported-types/callables-and-closures' },
4741
{ text: 'Iterators & Generators', link: '/supported-types/iterators-and-generators' },
4842
{ text: 'Unions, Intersections & Conditionals', link: '/supported-types/unions-intersections-and-conditionals' },
43+
{ text: 'Type Aliases', link: '/supported-types/type-aliases' },
44+
]
45+
},
46+
{
47+
text: 'Runtime Generics',
48+
items: [
49+
{ text: 'Generics & Bounds', link: '/generics/generics-and-bounds' },
4950
]
5051
},
5152
{
52-
text: 'Advanced Features',
53+
text: 'Advanced & Architecture',
5354
items: [
55+
{ text: 'How It Works', link: '/advanced/how-it-works' },
5456
{ text: 'Liskov & Inheritance', link: '/advanced/liskov-and-inheritance' },
5557
{ text: 'Vendor Isolation', link: '/advanced/vendor-and-path-filtering' },
5658
{ text: 'Ignore Annotations', link: '/advanced/ignore-annotations' },
5759
{ text: 'Extensions', link: '/advanced/extensions' },
5860
{ text: 'Exception Handling', link: '/advanced/exception-handling' },
59-
{ text: 'Troubleshooting & FAQ', link: '/advanced/troubleshooting' }
6061
]
6162
},
6263
{
63-
text: 'Production & Performance',
64+
text: 'Production & Operations',
6465
items: [
6566
{ text: 'Production Readiness', link: '/production/production-readiness' },
66-
{ text: 'Cache CLI Commands', link: '/production/cache-commands' },
6767
{ text: 'Performance Considerations', link: '/production/performance-considerations' },
6868
]
69+
},
70+
{
71+
text: 'Help & Support',
72+
items: [
73+
{ text: 'Troubleshooting & FAQ', link: '/troubleshooting' },
74+
]
6975
}
7076
],
7177
socialLinks: [
File renamed without changes.

docs/advanced/liskov-and-inheritance.md

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -292,35 +292,42 @@ $service->update(10, 'Charlie');
292292

293293
---
294294

295-
## Parameter Renaming ($id $\rightarrow$ $userId$)
295+
## Parameter Renaming ($id $userId) & Position Shifts
296296

297-
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:
297+
When a child class or attribute constructor overrides a parent method, parameter positions or parameter names may shift. TypePHP resolves parameter contract inheritance using **Name-First Resolution**:
298+
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.
298301

299302
```php
300-
interface UserApiInterface
303+
class BaseField
301304
{
302305
/**
303-
* Interface uses parameter name $id
306+
* Parent constructor has $api at position #1
304307
*
305-
* @param positive-int $id
308+
* @param string $type
309+
* @param bool|array{admin-api: bool} $api
306310
*/
307-
public function find(int $id): bool;
311+
public function __construct(string $type, bool|array $api = false) {}
308312
}
309313

310-
class UserApi implements UserApiInterface
314+
class OneToManyRelation extends BaseField
311315
{
312-
// Child renames parameter $id to $userId
313-
public function find(int $userId): bool
314-
{
315-
return true;
316+
/**
317+
* Child inserts $entity, $ref, $onDelete BEFORE $api (position shift!)
318+
*/
319+
public function __construct(
320+
string $entity,
321+
string $ref,
322+
OnDeleteOption $onDelete = OnDeleteOption::NO_ACTION,
323+
bool|array $api = false
324+
) {
325+
parent::__construct('one-to-many', $api);
316326
}
317327
}
318328

319-
$api = new UserApi();
320-
321-
// $userId = -50 is checked at index 0 against interface's @param positive-int $id!
322-
$api->find(-50);
323-
// Throws: TypeError: UserApi::find(): Argument $userId must be of type positive-int
329+
// $onDelete (position #2 in child) is NOT overwritten by $api's type (position #1 in parent)!
330+
$attr = new OneToManyRelation('unit', 'unit_id', OnDeleteOption::CASCADE, true);
324331
```
325332

326333
---
File renamed without changes.

docs/core-concepts/function-contracts.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@ registerUser(-5, 'Alice', 'admin');
3535

3636
> **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.
3737
38+
---
39+
## PHP 8.0+ Named Arguments
40+
41+
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:
42+
43+
```php
44+
<?php
45+
46+
declare(strict_types=1);
47+
48+
/**
49+
* @param positive-int $id
50+
* @param non-empty-string $username
51+
* @param int<1, 100> $age
52+
*/
53+
function registerUser(int $id, string $username, int $age): void
54+
{
55+
// ...
56+
}
57+
58+
// Valid Call: Arguments passed in completely reversed/swapped order
59+
registerUser(age: 25, username: 'Alice', id: 42);
60+
61+
// Invalid Call: $id (-5) passed as 3rd named argument
62+
registerUser(age: 25, username: 'Alice', id: -5);
63+
// Throws: TypeError: registerUser(): Argument $id must be of type positive-int, negative int (-5) given
64+
```
3865
---
3966

4067
## Class Methods (Instance & Static)
File renamed without changes.

docs/index.md

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ layout: home
44
hero:
55
name: "TypePHP"
66
text: "Transparent Runtime Type Enforcement"
7-
tagline: "The first pure PHP library to enforce DocBlock types at runtime transparently without introducing any new syntax. Validates generics, array shapes, and advanced type contracts during execution."
7+
tagline: "No transpilation. No build steps. No C-extensions. Just 100% pure PHP that makes your existing DocBlocks scream the moment types fail."
88
actions:
99
- theme: brand
1010
text: "Get Started →"
@@ -16,17 +16,52 @@ hero:
1616
features:
1717
- title: "Zero Production Overhead"
1818
details: "Install as a development dependency to enforce strict types during local testing and CI/CD pipelines, guaranteeing absolute zero performance cost in live production environments."
19+
- title: "No Transpilation or C-Extensions"
20+
details: "Operates 100% in pure PHP user-land using native stream wrappers and AST transformations. No build scripts, Node.js tools, or C-extensions required."
1921
- title: "True Runtime Generics"
2022
details: "Binds generic template types to specific object instances dynamically using native WeakMap memory tracking."
21-
- title: "Typed Arrays & Shapes"
22-
details: "Deeply validates sequential lists, typed class arrays, and strict associative array shape structures right out of the box."
23-
- title: "PHP 8.4 Support"
24-
details: "Native support for intercepting and validating PHP 8.4 Property Hooks (get/set) and Asymmetric Visibility (public private(set))."
23+
- title: "Arrays, Shapes & Extractions"
24+
details: "Deeply validates sequential lists, typed arrays, array shapes, and key-of / value-of constant extractions out of the box."
2525
---
2626

27+
::: tip Pure PHP • Zero Transpilation • Zero Build Steps
28+
**You don't have to change a single line of code, and you don't need a compilation build toolchain.** TypePHP operates entirely in native PHP user-land and no custom PHP binaries, C-extensions, or Node.js transpilers needed. Drop TypePHP into your existing project, run your code, and your DocBlocks will instantly start screaming at runtime when dynamic data violates a type contract.
29+
:::
30+
2731
## See It In Action
2832

29-
TypePHP is the first pure PHP library that operates entirely in user-land using native stream wrappers and AST transformations. Because it requires no C-extensions or FFI, you can drop it into any PHP 8.1+ project effortlessly. It parses your standard PHPDoc annotations and enforces them the moment your code runs.
33+
TypePHP operates entirely in user-land using native stream wrappers and AST transformations. Because it requires no C-extensions or FFI, you can drop it into any PHP 8.1+ project or web framework effortlessly. It reads your existing PHPDoc annotations and enforces them the moment your code runs.
34+
35+
### Real-World Framework Guard Rails (Laravel / Symfony)
36+
Prevent dynamic data bugs from leaking into database queries or API responses:
37+
38+
```php
39+
namespace App\Models;
40+
41+
use App\Enums\Role;
42+
use Illuminate\Database\Eloquent\Model;
43+
44+
class User extends Model
45+
{
46+
/**
47+
* @return list<int>
48+
*/
49+
public function assignableRoles(): array
50+
{
51+
if ($this->isSuperAdmin()) {
52+
// Bug! Returns an array of Role Enum instances instead of integers:
53+
return Role::cases();
54+
}
55+
56+
return [Role::STAFF->value];
57+
}
58+
}
59+
60+
// Executing $user->assignableRoles() throws:
61+
// TypePHP\Exception\TypeError: User::assignableRoles(): Return value[0] must be of type int, App\Enums\Role returned
62+
```
63+
64+
---
3065

3166
### True Runtime Generics
3267
Define generic templates and TypePHP will track their state in memory per object instance:
@@ -51,68 +86,53 @@ $users->add(new Product('SKU-100'));
5186
// Throws TypeError: Argument $item (template T = User) must be of type User, Product given
5287
```
5388

54-
### Array Shapes & Typed Arrays
55-
Enforce strict associative array structures and collections of specific objects:
56-
57-
```php
58-
/**
59-
* @param array{status: 'active'|'pending', tags: list<non-empty-string>} $options
60-
* @param User[] $collaborators
61-
*/
62-
function processBatch(array $options, array $collaborators): void
63-
{
64-
// ...
65-
}
89+
---
6690

67-
processBatch(
68-
options: ['status' => 'active', 'tags' => ['php', 'types']],
69-
collaborators: [new User(), new User()]
70-
); // Valid
91+
### Array Shapes & Key/Value Extractions
92+
Enforce strict associative array structures and constant extractions:
7193

72-
processBatch(
73-
options: ['status' => 'archived', 'tags' => ['php']],
74-
collaborators: []
75-
);
76-
// Throws TypeError: Argument $options['status'] must be of type ('active' | 'pending')
77-
```
94+
```php
95+
namespace App\Services;
7896

79-
### Scalar Refinements & Function Boundaries
80-
Catch invalid parameters before your function executes, and invalid return values before they leak out:
97+
use App\Database\DriverManager;
8198

82-
```php
8399
/**
84-
* @param positive-int $id
85-
* @return non-empty-string
100+
* @phpstan-type ConnectionParams array{
101+
* driver: key-of<DriverManager::DRIVER_MAP>,
102+
* driverClass?: value-of<DriverManager::DRIVER_MAP>
103+
* }
86104
*/
87-
function generateUserToken(int $id): string
105+
class DatabaseService
88106
{
89-
return ""; // Throws TypeError: Return value must be of type non-empty-string
107+
/**
108+
* @param ConnectionParams $params
109+
*/
110+
public function connect(array $params): void
111+
{
112+
// ...
113+
}
90114
}
91115

92-
generateUserToken(-5);
93-
// Throws TypeError: Argument $id must be of type positive-int, negative int (-5) given
116+
$service = new DatabaseService();
117+
118+
$service->connect(['driver' => 'pdo_mysql']); // Valid
119+
120+
$service->connect(['driver' => 'pdo_invalid']);
121+
// Throws TypeError: Argument $params['driver'] must be a key of DriverManager::DRIVER_MAP
94122
```
95123

96124
---
97125

98-
## Precise Stack Trace & Error Reporting
126+
## Precise Call-Site Trace Attribution
99127

100-
TypePHP injects single-line guard rails without shifting your source file line numbers.
128+
A common problem with AST code injection is that adding new statements pushes subsequent code down, causing line numbers in stack traces to drift out of sync.
101129

102-
When an inline variable or type contract fails, framework error handlers and test runners (like Pest, PHPUnit, and Whoops) point **directly to the exact line number** where the invalid assignment or argument occurred in your application code:
130+
TypePHP solves this with **Zero Line-Drift Formatting**. Injected guard rails are squashed onto single lines and appended directly to existing code blocks. **Line numbers in your source files remain 100% identical before and after transformation.**
103131

104-
```
105-
FAILED Tests\SomeTest > test
106-
107-
TypeError: Variable $typeArray[3] must be of type int, string '1' given
108-
109-
at tests/SomeTest.php:7
110-
3| declare(strict_types=1);
111-
4|
112-
5| test('test', function () {
113-
6| /** @var array<int> */
114-
➜ 7| $typeArray = [1, 2, 3, '1'];
115-
8|
116-
9| expect($typeArray)->toBeArray();
117-
10| });
118-
```
132+
When a type contract fails, web exception handlers (**Laravel Ignition, Whoops, Symfony ErrorHandler**) and CLI test runners (**Pest, PHPUnit**) point **directly to the exact line number** where the invalid assignment or return value occurred in your application code:
133+
134+
### Web Framework Trace (Laravel Ignition)
135+
![Laravel Ignition Exception Trace](/laravel-error-screen.png)
136+
137+
### CLI Test Runner Trace (Pest PHP)
138+
![Pest CLI Exception Trace](/pest-error-screen.png)
76.4 KB
Loading

0 commit comments

Comments
 (0)