Tip
Because parent(...) is called before the constructor body executes, the parent always receives the original parameter values. This prevents subtle bugs where a developer modifies a parameter before calling parent::__construct().
Tier 0 · Story 06Complete
Tyhp adds new syntax to class declarations that does not exist in PHP. This includes constructor return types for explicit constructor chaining, promoted constructor parameters, and property accessors. These features make class declarations more expressive and self-documenting.
In PHP, constructors never have a return type. Tyhp requires a constructor return type: : void when the constructor does not chain to a parent, or : parent(args...) when it does. Omitting both is a parse error.
Every constructor that does not use : parent(...) must be declared : void. That is required grammar, not optional documentation. : void means this constructor does not insert a parent::__construct(...) call.
<?tyhp
class SimpleClass
{
public function __construct(
public string $name,
public int $age
): void {}
}
The : void annotation is stripped from the PHP output. The constructor is emitted as a standard PHP constructor with no return type.
<?php
class SimpleClass
{
public function __construct(
public string $name,
public int $age
) {}
}
The : parent(args...) syntax calls the parent constructor at the START of the child constructor body. The arguments in the parent(...) call are passed to parent::__construct(). This replaces the manual parent::__construct(...) call that PHP developers write by hand and guarantees it always runs first.
<?tyhp
class BaseModel
{
public function __construct(
protected bool $isActive,
protected int $priority,
protected string $label
): void {}
}
class UserModel extends BaseModel
{
public string $displayName;
public function __construct(string $name): parent(false, 19, $name)
{
$this->displayName = \strtoupper($name);
}
}
The : parent(args...) annotation compiles to a parent::__construct(args...) call inserted at the very beginning of the constructor body. The return type annotation is removed.
<?php
class BaseModel
{
public function __construct(
protected bool $isActive,
protected int $priority,
protected string $label
) {}
}
class UserModel extends BaseModel
{
public string $displayName;
public function __construct(string $name)
{
parent::__construct(false, 19, $name);
$this->displayName = \strtoupper($name);
}
}
An important detail: the arguments passed to parent(...) use the original incoming parameter values, not values modified within the constructor body. Even if a parameter is reassigned inside the constructor, the parent receives the value as it was when the constructor was called.
<?tyhp
class Child extends BaseModel
{
public string $processed;
public function __construct(string $input): parent(true, 5, $input)
{
$input = \strtolower($input);
$this->processed = $input;
}
}
<?php
class Child extends BaseModel
{
public string $processed;
public function __construct(string $input)
{
parent::__construct(true, 5, $input);
$input = \strtolower($input);
$this->processed = $input;
}
}
Because parent(...) is called before the constructor body executes, the parent always receives the original parameter values. This prevents subtle bugs where a developer modifies a parameter before calling parent::__construct().
Because parent(...) is inserted at the start of the constructor body, prefer literals, constants, and incoming parameters. Avoid $this and method calls in those arguments — the instance is not fully constructed yet. The compiler does not currently reject those expressions; treat this as guidance, not a hard check.
<?tyhp
class Vehicle extends BaseModel
{
public function __construct(
string $type,
int $year
): parent(true, $year, $type) {}
}
class DefaultVehicle extends BaseModel
{
public function __construct(): parent(false, 0, 'unknown') {}
}
<?php
class Vehicle extends BaseModel
{
public function __construct(
string $type,
int $year
) {
parent::__construct(true, $year, $type);
}
}
class DefaultVehicle extends BaseModel
{
public function __construct()
{
parent::__construct(false, 0, 'unknown');
}
}
Constructor property promotion works as in PHP 8. Constructor parameters may be promoted with visibility and readonly. Use readonly when a property should be set only during construction. clone $obj with [...] can still update readonly properties on the copy; see the with keyword page.
<?tyhp
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
public readonly float $z = 0.0
): void {}
}
$p = new Point(1.0, 2.0);
$q = clone $p with [z => 5.0];
Tyhp supports PHP 8.4 property hooks (get / set) on class properties. Hook bodies match PHP: a parameter-less get { ... }, optional set { ... } (the incoming value is $value), or a typed set(string $value) { ... }. &get and final get / final set are also valid.
<?tyhp
final class Widget {
private string $_name = '';
public string $name {
get {
return $this->_name;
}
set(string $value) {
$this->_name = $value;
}
}
}
On PHP 8.4+, the compiler emits native hook syntax. On PHP 8.2–8.3, it lowers the same source through a polyfill (\Tyhp\Concerns\UsesPropertyAccessors and $this->__tyhpPropertyHook) so hooked properties work without native PHP 8.4 hooks.
Every constructor must be : void or : parent(...). Use : void when there is no parent call; use : parent(args...) when chaining so the parent constructor always runs first.
Use : void on constructors that do not chain — it is required, and it is stripped from PHP output.
Use constructor property promotion with readonly for concise, immutable data classes. Update copies with clone $obj with [...].
Do not use : parent(...) on a class that has no parent class — the emitter still inserts parent::__construct(...), which PHP will reject at runtime. Diagnostic TYHP4065 (CheckerParentWithoutParent) is for the type name parent, not this constructor-chaining form.
Prefer not to use $this or method calls in parent(...) arguments — the parent call runs before the constructor body. The compiler does not currently reject those expressions.
Do not also write a manual parent::__construct() when using : parent(...) — the compiler already inserts the call, so a second call would run the parent constructor twice. The compiler does not currently diagnose the duplicate.
<?tyhp
// Avoid: no parent class to chain to (emits parent::__construct anyway)
// class NoParent {
// public function __construct(): parent() {}
// }
// Avoid: $this / method calls in parent args (not a compiler error today)
// class Bad extends BaseModel {
// public function __construct(string $name): parent(true, 0, $this->process($name)) {}
// }
// Avoid: double parent call (not a compiler error today)
// class Double extends BaseModel {
// public function __construct(string $name): parent(true, 0, $name) {
// parent::__construct(true, 0, $name);
// }
// }