Function Contracts
Functions and methods form the public boundaries of your software modules. TypePHP enforces @param and @return annotations directly at function entry and exit points.
Parameter Contracts (@param)
When you declare @param annotations on a function or class method, TypePHP validates all incoming arguments before entering the function body:
Suppressing Function Contracts: Need to skip type-checking on a legacy function or method? Add
@typephp-ignoreto its docblock. See Ignore Annotations for full details.
<?php
declare(strict_types=1);
/**
* @param positive-int $id
* @param non-empty-string $username
* @param 'admin'|'editor'|'viewer' $role
*/
function registerUser(int $id, string $username, string $role): void
{
// Executed only if all arguments pass validation
}
// Valid Call
registerUser(100, 'Alice', 'admin');
// Invalid Call (Passing negative integer)
registerUser(-5, 'Alice', 'admin');
// Throws: TypeError: registerUser(): Argument $id must be of type positive-int, negative int (-5) givenExecution 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 nativeTypeErrorbefore TypePHP's guard rails run.
Class Methods (Instance & Static)
All parameter and return contract rules apply identically to instance methods (public, protected, private) and static methods:
class UserService
{
/**
* Instance Method Contract
*
* @param positive-int $id
* @return array{id: positive-int, name: non-empty-string}
*/
public function findUser(int $id): array
{
return ['id' => $id, 'name' => 'Alice'];
}
/**
* Static Method Contract
*
* @param non-empty-string $role
* @return list<positive-int>
*/
public static function getRoleIds(string $role): array
{
return [10, 20, 30];
}
}
$service = new UserService();
// Invalid Instance Method Call ($id is negative)
$service->findUser(-10);
// Throws: TypeError: UserService::findUser(): Argument $id must be of type positive-int
// Invalid Static Method Call ($role is empty string)
UserService::getRoleIds('');
// Throws: TypeError: UserService::getRoleIds(): Argument $role must be of type non-empty-stringClass Constructors (__construct)
TypePHP fully validates class constructor arguments, supporting both standard constructors and Constructor Property Promotion (PHP 8.0+).
Promoted Properties (PHP 8.0+)
Annotate promoted properties in the constructor's docblock using standard @param tags:
class Order
{
/**
* @param positive-int $id
* @param non-empty-string $sku
* @param int<1, 100> $quantity
*/
public function __construct(
public int $id,
public string $sku,
public int $quantity
) {}
}
// Valid Instance
new Order(1, 'SKU-99', 5);
// Invalid Instance ($id is negative)
new Order(-1, 'SKU-99', 5);
// Throws: TypeError: Order::__construct(): Argument $id must be of type positive-intProperty @var Fallback for Un-Annotated Constructors
If a constructor parameter is un-annotated (or lacks a @param tag), TypePHP automatically inspects the corresponding class property's @var docblock to infer the parameter contract:
class User
{
/**
* @var string[]
*/
public array $roles;
// Un-annotated constructor parameter inherits contract from $roles property docblock!
public function __construct(array $roles)
{
$this->roles = $roles;
}
}
// Invalid Instance (element 1 is an integer)
new User(['admin', 12345]);
// Throws: TypeError: User::__construct(): Argument $roles[1] must be of type string, int (12345) givenReturn Contracts (@return)
TypePHP validates return statements before values are returned to the caller:
/**
* @return array{id: positive-int, status: 'active'|'pending'}
*/
function getUserStatus(int $id): array
{
if ($id <= 0) {
return ['id' => $id, 'status' => 'active']; // Invalid: $id is negative
}
return ['id' => $id, 'status' => 'active'];
}
getUserStatus(-10);
// Throws: TypeError: getUserStatus(): Return value['id'] must be of type positive-intPHPStan and Psalm Compatibility: TypePHP also recognizes
@phpstan-param,@phpstan-return,@psalm-param, and@psalm-returnannotations.
Variadic Parameter Contracts
When a function or method accepts variadic arguments (...$items), TypePHP validates every element passed in the variadic argument list:
/**
* @param positive-int ...$ids
*/
function deleteUsers(int ...$ids): void
{
// ...
}
// Valid Call
deleteUsers(10, 20, 30);
// Invalid Call (3rd variadic item violates positive-int)
deleteUsers(10, 20, -5);
// Throws: TypeError: deleteUsers(): Argument $ids[2] must be of type positive-intFluent $this Identity Returns
For fluent builder or service classes annotated with @return $this, TypePHP verifies strict object identity ($result === $this), preventing accidental instantiation of new instances:
class UserBuilder
{
private string $name = '';
/**
* @return $this
*/
public function setName(string $name): self
{
$this->name = $name;
return $this; // Valid: Strict $this identity
}
/**
* @return $this
*/
public function cloneSelf(): self
{
return new self(); // Invalid: New instance returned instead of $this
}
}
$builder = new UserBuilder();
$builder->cloneSelf();
// Throws: TypeError: UserBuilder::cloneSelf(): Return value must be $this instanceConditional Return Types
TypePHP supports parameter-based conditional return types (@return ($param is true ? TypeA : TypeB)):
/**
* @param bool $asInt
* @param mixed $value
* @return ($asInt is true ? positive-int : non-empty-string)
*/
function formatValue(bool $asInt, mixed $value): mixed
{
return $value;
}
// Valid Calls
formatValue(true, 42); // Evaluates return type as positive-int
formatValue(false, 'hello'); // Evaluates return type as non-empty-string
// Invalid Call
formatValue(true, 'not_an_int');
// Throws: TypeError: formatValue(): Return value must be of type positive-intPHP 8.0+ Attributes Coexistence
TypePHP seamlessly coexists with native PHP 8.0+ Attributes (#[Route], #[Inject], #[Validate]).
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:
// Option A: DocBlock ABOVE Attribute (Supported)
/**
* @param positive-int $id
* @return array{id: positive-int, username: non-empty-string}
*/
#[Route('/user/{id}', method: 'GET')]
public function showUser(int $id): array
{
return ['id' => $id, 'username' => 'Alice'];
}
// Option B: DocBlock BELOW Attribute (Supported)
#[Route('/user/{id}', method: 'GET')]
/**
* @param positive-int $id
* @return array{id: positive-int, username: non-empty-string}
*/
public function showUser(int $id): array
{
return ['id' => $id, 'username' => 'Alice'];
}