Tip
Use trait requirements to enforce contracts — if a trait relies on methods from a base class or interface, declare those requirements explicitly.
Tier 0 · Story 08Complete
In PHP, traits cannot specify that the using class must extend a specific base class or implement certain interfaces. Tyhp adds extends and implements support directly on trait declarations as compile-time constraints. These requirements are validated at compile time and completely erased from the PHP output. This is similar to Hack's require extends and require implements.
A trait can declare that any class using it must extend a specific base class. This allows the trait to safely use methods and properties from that base class, because the compiler guarantees the using class extends it.
<?tyhp
trait TimestampedEntity extends Entity
{
protected \DateTime $createdAt;
protected \DateTime $updatedAt;
public function touch(): void
{
$this->updatedAt = new \DateTime();
}
public function getEntityLabel(): string
{
// Safe: compiler guarantees Entity::getId() is available
return static::class . ':' . $this->getId();
}
}
A trait can declare that any class using it must implement one or more interfaces. This ensures that the trait's methods can rely on interface methods being present on the using class.
<?tyhp
trait Cacheable extends Entity implements Serializable
{
public function getCacheKey(): string
{
return static::class . ':' . $this->getId();
}
public function toCacheValue(): string
{
// Safe: Serializable::serialize() is guaranteed
return $this->serialize();
}
}
The class using the trait must satisfy all declared requirements. The compiler validates this at compile time and emits an error if any requirement is not met.
<?tyhp
// This is OK: User extends Entity and implements Serializable
class User extends Entity implements Serializable
{
use Cacheable;
use TimestampedEntity;
public function serialize(): string { /* ... */ }
public function unserialize(string $data): void { /* ... */ }
}
// COMPILE ERROR: Post does not extend Entity
// class Post {
// use Cacheable; // Error: Cacheable requires extends Entity
// }
// COMPILE ERROR: Comment extends Entity but doesn't implement Serializable
// class Comment extends Entity {
// use Cacheable; // Error: Cacheable requires implements Serializable
// }
Trait requirements are compile-time only. The extends and implements clauses are completely stripped from the PHP output. The trait is emitted as a standard PHP trait.
<?tyhp
trait Cacheable extends Entity implements Serializable
{
public function getCacheKey(): string
{
return static::class . ':' . $this->getId();
}
}
<?php
trait Cacheable
{
public function getCacheKey(): string
{
return static::class . ':' . $this->getId();
}
}
Tyhp extends PHP's trait aliasing to support property aliasing. In PHP, you can alias trait methods using as; in Tyhp, you can also alias trait properties using TraitName::$property as $newName. This is useful when two traits define a property with the same name.
<?tyhp
trait HasName
{
public string $name = '';
}
trait HasLabel
{
public string $name = '';
}
class Widget
{
use HasName, HasLabel {
HasLabel::$name as $label;
}
// Now $this->name comes from HasName
// and $this->label comes from HasLabel (aliased)
}
PHP cannot alias properties in a use adaptation block. This alpha still parses HasLabel::$name as $label and emits it as a PHP use alias (HasLabel::$name as $label), which PHP will reject. It does not rewrite the alias into a separate property declaration. Prefer distinct property names on the traits, or copy the value in the class constructor, until emit is fixed.
A trait can require at most one base class via extends (single inheritance), but can require multiple interfaces via implements.
<?tyhp
trait FullyManaged extends Entity implements Serializable, JsonSerializable
{
public function toManagedArray(): array
{
return [
'id' => $this->getId(),
'serialized' => $this->serialize(),
'json' => $this->jsonSerialize(),
];
}
}
Use trait requirements to enforce contracts — if a trait relies on methods from a base class or interface, declare those requirements explicitly.
Document trait requirements even though they are compile-time only — they serve as documentation for which classes can use the trait.
Use trait property aliasing to resolve property name conflicts between traits instead of restructuring your trait hierarchy.
Prefer implements requirements over extends requirements when possible — interface requirements are more flexible and don't tie the trait to a specific class hierarchy.
Do not assume trait requirements appear in PHP output — they are compile-time only and completely erased. The PHP trait has no extends or implements clause.
Do not specify multiple base classes with extends on a trait — PHP has single inheritance, so only one base class is allowed.
Do not use a trait on a class that does not satisfy its requirements — the compiler emits an error at compile time.
<?tyhp
// ERROR: Cannot require multiple base classes
// trait Bad extends ClassA, ClassB { }
// ERROR: class does not extend Entity
// class StandaloneWidget {
// use TimestampedEntity; // Error: requires extends Entity
// }