Tip
DO declare only public and protected members. Private members are not accessible from Tyhp code and should be omitted from your Tyhpdef declarations.
Tier 0 · Story 02Complete
Tyhpdef lets you describe existing PHP classes to the Tyhp compiler. When importing a class, you declare its properties, methods, constants, and relationships (extends, implements). You only need to declare the members you actually use in your Tyhp code — members already defined via an imported interface do not need to be re-declared.
Consider a PHP class you want to use from Tyhp:
<?php
class Customer implements Person {
protected $data;
public $heightInCentimeters = 23;
public function __construct($data = []) {
$this->data = $data;
}
// ... implements Person methods ...
}
The Tyhpdef declaration provides full type information. Methods are signatures only, ending with semicolons. Optional ?? on a property is the value PHP is expected to start with; the live value may differ, and tyhpdef does not define it.
<?tyhpdef
type CustomerData = struct {
?string $first_name;
?string $last_name;
};
class Customer implements Person {
public float $heightInCentimeters ?? 23;
protected CustomerData $data;
public function __construct(CustomerData $data = []): void;
// Members from the Person interface are inherited
// and do not need to be re-declared here.
}
Classes can be declared with abstract or final modifiers. These modifiers control how Tyhp sees the class at compile time. Make sure they match the actual PHP class — declaring a non-final PHP class as final in Tyhpdef will prevent valid subclassing in Tyhp.
<?tyhpdef
abstract class Entity {
protected int $id;
public function getId(): int;
abstract public function getTableName(): string;
}
final class ImmutableConfig {
public function get(string $key): mixed;
public function has(string $key): bool;
}
Properties are declared with visibility modifiers and types. Only public and protected properties should be declared — private properties are not accessible from Tyhp code.
Optional ?? <value> after the name is the expected PHP start value (protected ?\Psr\Log\LoggerInterface $logger ?? null;, private array $items ?? [];). Tyhpdef does not assign the property; the live value may differ. Do not write = <value> on tyhpdef properties (or on tyhpdef consts). Parameter defaults still use =.
When a PHP constructor promotes a parameter, declare the property with ?? and keep = on the parameter:
public int $count ?? 0;
function __construct(int $count = 0): void;
Do not write function __construct(public int $x ?? 0).
Storage properties end with a semicolon. Hooked PHP properties (classes, interfaces, and traits) use a bodyless hook list: { get; set; }, { get; }, or { &get; set; }. The list is signatures only — no get { } or get => bodies. A hooked declaration names one $name. Optional ?? <value> sits before the hook list (public string $name ?? "x" { get; set; }). Hooks may include visibility (private set) and final. Put #[\Tyhp\Php] on the property (or wrap with declare(php=…)), not on an individual hook (TYHP8016).
<?tyhpdef
class Product {
public int $id;
public string $name;
public float $price ?? 0.0;
public ?string $description ?? null;
protected array<string> $tags ?? [];
public readonly string $sku;
}
class Holder {
public string $hooked { get; set; }
public int $hookedCount { get; }
public array $refItems { &get; set; }
public string $displayName { get; private set; }
}
Method declarations include the full signature: visibility, optional modifiers (static, abstract, final), the function keyword, parameters with types, and return type. All methods end with a semicolon.
<?tyhpdef
class UserService {
public function find(int $id): ?\App\Models\User;
public function findByEmail(string $email): ?\App\Models\User;
public static function create(string $name, string $email): \App\Models\User;
abstract protected function validate(\App\Models\User $user): bool;
final public function delete(int $id): void;
}
tyhp generate_tyhpdef writes PHP static as self on parameters and properties (@param, @var, magic @method parameters, @property) and keeps static on return types (: static, magic @method returns, including generic arguments such as Builder<static>). Harvested method parameters keep optional = with a tyhpdef-safe literal, or = null when the PHP default is not representable — the same dummy used for harvested implicit-nullable PHP (string $timezone = null). The declared type is not widened. See CLI: Tyhpdef Generation.
Constructors are declared using __construct and can specify a return type of : void. Constructor parameter promotion (public/protected/private on parameters) is supported. Promoted parameters keep = for the parameter default; the cloned property uses ?? for PHP's expected start value (public int $count ?? 0; plus function __construct(int $count = 0): void;).
<?tyhpdef
class DatabaseConnection {
public function __construct(
string $host,
int $port = 3306,
?string $database = null
): void;
public function query(string $sql): array<mixed>;
public function close(): void;
}
Class constants are declared with visibility, the const keyword, a type, and a name. Optional ?? <value> is the expected PHP start value (same meaning as on properties). Do not write = <value> on a tyhpdef const.
A child class that redeclares the same constant must keep that type. Omitting the type on the child infers it from the ancestor that first declared the constant. A different type is an error.
<?tyhpdef
class HttpStatus {
public const int OK ?? 200;
public const int NOT_FOUND;
public const int INTERNAL_ERROR;
public const string DEFAULT_CONTENT_TYPE;
}
Classes can extend a parent class and implement one or more interfaces, mirroring PHP's inheritance model.
<?tyhpdef
class AdminUser extends \App\Models\User implements \Stringable, \JsonSerializable {
public function getRole(): string;
public function __toString(): string;
public function jsonSerialize(): mixed;
}
Classes support generic type parameters with optional constraints. Generics enable type-safe descriptions of PHP classes that work with different types.
<?tyhpdef
class Collection<T> {
public function add(T $item): void;
public function get(int $index): T;
public function count(): int;
public function toArray(): array<T>;
public function filter(callable(T): bool $predicate): Collection<T>;
public function map<U>(callable(T): U $transform): Collection<U>;
}
// Generic class with constraint and extends
class TypedRepository<T extends Entity> extends Repository<T> {
public const int DEFAULT_PAGE_SIZE;
public function paginate(int $page, int $size): array<T>;
async public function saveAsync(T $entity): T;
}
tyhp generate_tyhpdef writes PHPDoc @template tags as these headers. Unqualified class names in a bound (@template T of Repository<object>) become FQCNs using use imports, unique PHP / \Psr\* shorts, and types declared in the same package. Native extends / implements / trait use prefer a same-package declared type over a unique PHP / \Psr\* short of the same name. Template parameters (T) and builtins stay unqualified. A bound written without type arguments is completed from that type's own default, else its constraint, else mixed. See CLI: Tyhpdef Generation.
Classes in Tyhpdef can declare trait usage. Trait conflict resolution and aliasing syntax works the same as in PHP.
<?tyhpdef
class AuditableUser {
use TimestampedEntity, SoftDeletes {
SoftDeletes::delete insteadof TimestampedEntity;
TimestampedEntity::delete as hardDelete;
}
public function getAuditLog(): array<string>;
}
When a PHP type already supports an operator at runtime (engine magic, a PECL extension, DateTime comparison, and similar), declare it as a bodyless operator member. Tyhp type-checks expressions such as $a < $b against the signature. The emitter leaves the PHP operator in place — it does not rewrite the site to __add or any other method.
The declaration is signature-only: operator, the token, typed parameters, a return type, and a semicolon. There is no body and no visibility modifier.
<?tyhpdef
class Instant {
operator +(self $left, DateInterval $right): Instant;
operator <=>(self $left, Instant $right): int;
}
Binary operators take two parameters; unary operators take one. The same operator can be overloaded for different operand types, the same way methods can.
<?tyhpdef
class Decimal {
operator +(self $left, Decimal $right): self;
operator +(self $left, int|float|string $right): self;
operator +(self $value): int|float;
operator convert(int $value): self;
operator convert(self $value): string;
}
extension operator is a different member: it maps Tyhp operator usage onto PHP methods and requires a thin => expression. Use it when the PHP type has methods such as plus() but does not implement the operator natively. See Extensions in Tyhpdef. A bodyless extension operator +(…): T; is an error (TYHP8013).
partial is legal on tyhpdef class / enum / interface / trait in base and include files. It adds members to a type that already exists in this compilation’s include set.
extends, implements, or flags.TYHP8002).TYHP8014). Include-load order among additive partials does not matter (the type must exist somewhere in the include set).omit is illegal here (overlay-only, TYHP8017).<?tyhpdef
class Box {
public function get(): int;
}
partial class Box {
public function extra(): int;
}
<?tyhpdef
// ERROR TYHP8014: no matching type in the include set
// partial class DoesNotExist {
// public function extra(): void;
// }
// ERROR TYHP8002: duplicate member
// partial class Box {
// public function get(): int;
// }
Do not use partial to attach standalone extensions. See Extensions in Tyhpdef. Overlay files also use brace partial (last-wins member replace, missing target = warning), header-only partial class Foo<T> implements …; (written header clauses replace; omitted clauses and members stay), overlay partial function (attribute merge / rename), and omit. Header ; is overlay-only (TYHP8034). See Tyhpdef Overlays.
On tyhpdef classes (and interfaces, enums, traits, and their members), use #[\Tyhp\Php("…")] (positional or version:). File-level declare(php="…"); and brace declare(php="…") { … } also parse in .tyhpdef.
On a hooked property, put #[\Tyhp\Php] on the property, not on get or set (TYHP8016):
<?tyhpdef
#[\Tyhp\Php(">=8.4")]
public string $name { get; set; }
#[\Tyhp\Php] is illegal on struct and on tyhpdef extension { } (TYHP4304). See PHP Version Gating.
tyhpdef/php authors Closure, Fiber, Generator, and ArrayAccess generics in Layer 3 overlays (_tyhpdef/overlays/Ext.Core.tyhpdef). \Closure is \Closure<TCallableShape, TThis, TScope>; spell signatures as \Closure<callable(int): string>. \Fiber is Fiber<TResume, TCallableShape>. Generator functions declare \Generator / \Generator<K, V> / the four-arg form. See New and Changed Types.
PHP allows a class, interface, trait, or enum whose short name is a reserved word (is, isset, and similar). In a .tyhpdef, write that declaration with a leading-backslash fully-qualified name so the parser treats it as a name rather than a keyword:
<?tyhpdef
namespace Hamcrest\Core {
class \Hamcrest\Core\Is {
public function matches(mixed $item): bool;
}
}
tyhp generate_tyhpdef emits this form. Hand-written include and overlay files use the same spelling. Alias headers keep the PHP name fully qualified: class \Hamcrest\Core\Is as IsMatcher { … }. Inside a namespace { }, a harvested extern function whose short name is a reserved word is written the same way (extern function \Some\Ns\isset;).
When the PHP class name differs from what you want to use in Tyhp, you can alias it using the as keyword. The original fully-qualified PHP class name comes first, followed by as and the Tyhp alias. A reserved-word PHP name is fully qualified on the left of as as well (see Reserved-word type names).
<?tyhpdef
class \Vendor\LongNamespace\SomeVeryLongClassName as ShortName {
public function doWork(): void;
}
The class itself can be marked deprecated or obsolete (top-level). Member-level markers parse but are not enforced in this alpha.
<?tyhpdef
deprecated class LegacyAuth {
public function login(string $user, string $pass): bool;
}
class Auth {
public function loginWithPassword(string $pass): bool;
public function loginWithToken(string $token): bool;
}
DO declare only public and protected members. Private members are not accessible from Tyhp code and should be omitted from your Tyhpdef declarations.
DO match the abstract/final modifiers to the actual PHP class. Mismatched modifiers cause compile-time or runtime errors.
DO use bodyless operator …; when the PHP type already implements the operator natively. Do not give that member a body.
DON'T include ordinary method bodies, hook bodies (get { } / get =>), or = property/const initializers
in class declarations. Methods and storage properties end with a semicolon. Use ?? <value> when you
need to record PHP's expected start value. Hooked properties use a
bodyless { get; set; } list. Mapping Tyhp operators onto PHP methods uses extension operator
with a required => expression; see Extensions in Tyhpdef.
DON'T re-declare methods that are already defined on an imported interface the class implements. The compiler inherits those signatures automatically.