Skip to content
16

TypePHPTransparent Runtime Type Enforcement

The first pure userland PHP library to transparently enforce DocBlock types at runtime. Bringing reified generics, typed arrays, and 1-pass audit reporting to life with zero new syntax, zero build steps, and zero C-extensions.

Test Drive TypePHP Live in Your Browser (No Setup Required)

Want to see runtime reified generics and shape validation in action right now? Launch the Interactive WebAssembly Playground → running real PHP 8.5 in your browser. Experiment with presets, test your own edge cases, and inspect the transformed source code in real time!


See It In Action ​

TypePHP operates entirely in userland 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.

Real-World Framework Guard Rails (Laravel / Symfony) ​

Prevent dynamic data bugs from leaking into database queries or API responses:

php
namespace App\Models;

use App\Enums\Role;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * @return list<int>
     */
    public function assignableRoles(): array
    {
        if ($this->isSuperAdmin()) {
            // Bug! Returns an array of Role Enum instances instead of integers:
            return Role::cases(); 
        }

        return [Role::STAFF->value];
    }
}

// Executing $user->assignableRoles() throws:
// TypePHP\Exception\TypeError: User::assignableRoles(): Return value[0] must be of type int, App\Enums\Role returned

Non-Fatal Codebase Auditing & JSON Reports ​

Onboard TypePHP onto legacy codebases without fixing errors one crash at a time. Run your test suite in Audit Mode (TYPEPHP_ON_VIOLATION=report) to execute all tests to completion and capture every single type discrepancy across your application into a single structured JSON report:

bash
# 1. Run your test suite in non-fatal Audit Mode
TYPEPHP_ON_VIOLATION=report TYPEPHP_REPORT_FILE=var/typephp-report.json ./vendor/bin/pest

# 2. View clean formatted summary table in your terminal
vendor/bin/typephp report

Terminal Audit Summary Output (vendor/bin/typephp report) ​

text
  TYPEPHP  Violation Audit Report

  • Total Violations: 4
  • Files Affected:   3
  • Report Source:    var/typephp-report.json

  src/Services/PaymentService.php
    Line 42    parameter   $amount      expected positive-int, negative int (-50)

  src/Models/User.php
    Line 88    return      return       expected list<int>, App\Enums\Role returned

Learn More: Check out the Violation Reporting & Auditing guide for full details on parallel worker sharding and CI gatekeeping (fail_on_report).


True Runtime Generics with Reified Memory State ​

Define generic templates and TypePHP tracks their state per object instance in memory using native \WeakMap:

php
/**
 * @template T
 */
class Collection 
{
    /** @param T $item */
    public function add(mixed $item): void { /* ... */ }
}

// Prebind T = User to this specific object instance in memory
/** @var Collection<User> $users */
$users = new Collection();

$users->add(new User('Alice')); // Valid

$users->add(new Product('SKU-100')); 
// Throws TypeError: Argument $item (template T = User) must be of type User, Product given

Array Shapes & Key/Value Extractions ​

Enforce strict associative array structures and constant extractions:

php
namespace App\Services;

use App\Database\DriverManager;

/**
 * @phpstan-type ConnectionParams array{
 *     driver: key-of<DriverManager::DRIVER_MAP>,
 *     driverClass?: value-of<DriverManager::DRIVER_MAP>
 * }
 */
class DatabaseService
{
    /**
     * @param ConnectionParams $params
     */
    public function connect(array $params): void
    {
        // ...
    }
}

$service = new DatabaseService();

$service->connect(['driver' => 'pdo_mysql']); // Valid

$service->connect(['driver' => 'pdo_invalid']);
// Throws TypeError: Argument $params['driver'] must be a key of DriverManager::DRIVER_MAP

Precise Call-Site Trace Attribution (Zero Line-Drift) ​

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.

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.

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:

Web Framework Trace (Laravel Ignition) ​

Laravel Ignition Exception Trace

Web Framework Trace (Symfony ErrorHandler) ​

Symfony Exception Trace

CLI Test Runner Trace (Pest PHP) ​

Pest CLI Exception Trace