No transpilation. No build steps. No C-extensions.
Drop TypePHP into your existing codebase and let your DocBlocks scream when types fail.
TypePHP is a transparent, pure-PHP runtime type checker. You don't have to refactor a single line of your codebase, set up complex build toolchains, or compile C-extensions. 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.
Read the full TypePHP documentation »
Install TypePHP as a development dependency via Composer:
composer require typephp/typephp --devNote: TypePHP boots automatically via Composer's autoloader. No service providers, bootstrap edits, or framework configurations are required.
Generate a default typephp.php configuration file in your project root:
vendor/bin/typephp config:initAnnotate your functions, methods, and classes with standard static analysis DocBlocks:
namespace App\Services;
class PaymentService
{
/**
* @param positive-int $amount
* @param array{gateway: 'stripe'|'paypal', currency: non-empty-string} $options
* @return non-empty-string
*/
public function charge(int $amount, array $options): string
{
return "Transaction successful";
}
}If your application runs through an entry point where require 'vendor/autoload.php' is declared (such as a web framework's public/index.php, CLI commands, or test runners like Pest and PHPUnit), TypePHP runs automatically and transparently in the background.
Simply run your tests or use your local development server (Laravel, Symfony, FrankenPHP, PHP-FPM). TypePHP immediately intercepts and enforces all parameter, return, shape, and generic contracts in real time.
For standalone, single-execution PHP scripts that do not have an explicit autoloader entry point, run them directly with the TypePHP CLI binary:
vendor/bin/typephp script.phpBy default, TypePHP checks standard application folders (src/**, app/**, tests/**). To type-check PHP files anywhere in your project root while still respecting your excluded folders, set the "*" wildcard glob in typephp.php:
// typephp.php
return [
'include' => [
'*', // Intercepts and type-checks PHP files anywhere in the project
],
'exclude' => [
'vendor/**',
'storage/**',
'var/**',
'cache/**',
],
];When a type contract fails, web exception handlers (Laravel Ignition, Symfony ErrorHandler, Whoops) and CLI test runners (Pest, PHPUnit) highlight the exact line of code in your application where the invalid data was passed, with zero line-drift:
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 instance in WeakMap 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 key/value 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_MAPAll the documentation lives on the typephp-php.github.io/docs website:
- Getting Started & Installation Guide
- Quick Start Guide
- Configuration Guide
- CLI Commands Reference
- Runtime Generics (Flagship)
- Enforcement Boundaries: Function Contracts
- Supported Types: Arrays & Shapes
- Architecture: How It Works
- Official Blog & Announcements
- Troubleshooting & FAQ
TypePHP is conceptually inspired by Python's Beartype, bringing transparent runtime type enforcement for type annotations to the PHP ecosystem without any decorators or attributes.
Want to support the open-source development and maintenance of TypePHP? Sponsor me on GitHub »
Any contributions are welcome. Feel free to open issues or submit pull requests on GitHub.
TypePHP is open-source software licensed under the MIT License.


