Interactive Browser Playground
Test TypePHP live in your browser powered by real PHP 8.5 WebAssembly. Inspect zero line-drift AST transformations in real time with zero installation.
The first pure userland PHP library to transparently enforce DocBlock types at runtime. Bringing reified generics, typed arrays, and static analysis type refinements 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!
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.
Prevent dynamic data bugs from leaking into database queries or API responses:
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 returnedDefine generic templates and TypePHP tracks their state per object instance in memory using native \WeakMap:
/**
* @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 givenEnforce strict associative array structures and constant extractions:
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_MAPA 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:


