Classes
Classes, interfaces, abstract classes, traits, enums, properties, and inheritance.
Class declaration
<?php
class Point {
public $x;
public $y;
public function __construct($x, $y) {
$this->x = $x;
$this->y = $y;
}
public function magnitude() {
return sqrt($this->x * $this->x + $this->y * $this->y);
}
public static function origin() {
return new Point(0, 0);
}
}
Class, interface, trait, and method lookup is case-insensitive like PHP:
new point(), POINT::origin(), and $p->MAGNITUDE() resolve to Point and
its declared methods. Object properties remain case-sensitive, so $p->x and
$p->X are distinct property names.
Interfaces
<?php
interface Named {
public function name();
}
class Product implements Named {
public function name() { return "widget"; }
public function label() { return strtoupper($this->name()); }
}
- signature-only instance/static methods and PHP 8.4 property hook contracts; method and hook bodies are not allowed in interfaces
- interface inheritance flattened transitively with cycle detection
- static interface methods must be implemented by public static methods; instance and static methods cannot satisfy each other’s contracts
Interfaces may also declare static methods (PHP 8.3+). A concrete implementing
class must provide a compatible public static method (an instance method does
not satisfy the contract); an abstract class may defer it to a concrete child.
Dispatch is by class name — static interface methods take no vtable slot.
#[\Override] is accepted on a static implementation, matching the interface’s
static declaration.
<?php
interface Previewable {
public static function previews(): array;
}
class Card implements Previewable {
public static function previews(): array {
return ["front", "back"];
}
}
echo implode(",", Card::previews());
Interface properties must be hooked contracts. A concrete class can satisfy a { get; } contract with a public readable property, a { set; } contract with a public writable property, or both with an invariant public property. Get-only contracts allow covariant concrete types; set-only contracts allow contravariant concrete types.
<?php
interface HasName {
public string $name { get; set; }
}
class Product implements HasName {
public string $name = "widget";
}
Built-in interfaces
The compiler injects the following interfaces, available without any
implements declaration on the user side:
| Interface | Methods |
|---|---|
Traversable | (marker) |
Iterator extends Traversable | current(): mixed, key(): mixed, next(): void, valid(): bool, rewind(): void |
IteratorAggregate extends Traversable | getIterator(): Traversable |
OuterIterator extends Iterator | getInnerIterator(): ?Iterator |
RecursiveIterator extends Iterator | getChildren(): ?RecursiveIterator, hasChildren(): bool |
SeekableIterator extends Iterator | seek(int $offset): void |
Countable | count(): int |
ArrayAccess | offsetExists(mixed $offset): bool, offsetGet(mixed $offset): mixed, offsetSet(mixed $offset, mixed $value): void, offsetUnset(mixed $offset): void |
SplObserver | update(SplSubject $subject): void |
SplSubject | attach(SplObserver $observer): void, detach(SplObserver $observer): void, notify(): void |
Stringable | __toString(): string |
JsonSerializable | jsonSerialize(): mixed |
Throwable | getMessage(): string, getCode(): int, getFile(): string, getLine(): int, getTrace(): array, getTraceAsString(): string, getPrevious(): ?Throwable, __toString(): string |
count($obj) automatically dispatches to Countable::count() when
$obj is an instance of a class implementing Countable.
User classes cannot implement Throwable directly, matching PHP. Extend
Exception or Error instead; user interfaces may extend Throwable, and
classes that extend Exception or Error can implement those user interfaces.
Classes implementing ArrayAccess can use PHP subscript syntax:
$obj[$key] dispatches to offsetGet(), $obj[$key] = $value dispatches to
offsetSet(), isset($obj[$key]) dispatches to offsetExists(), and
unset($obj[$key]) dispatches to offsetUnset().
Serializable is intentionally not provided: it is deprecated since
PHP 8.1. Use the __serialize / __unserialize magic methods instead.
Built-in SPL containers and storage iterators
The SPL container and storage iterator classes are built-ins:
SplDoublyLinkedList, SplStack, SplQueue, SplFixedArray,
EmptyIterator, InternalIterator, ArrayIterator, ArrayObject, IteratorIterator,
LimitIterator, NoRewindIterator, InfiniteIterator, FilterIterator,
CallbackFilterIterator, CachingIterator, AppendIterator,
MultipleIterator, RecursiveArrayIterator, RecursiveFilterIterator,
RecursiveCallbackFilterIterator, RecursiveIteratorIterator, and
ParentIterator. They participate in class_exists(),
get_declared_classes(), instanceof, inherited class constants, interface
checks, foreach, and ArrayAccess where PHP expects it. PHP does not include
InternalIterator in spl_classes(), so elephc keeps it out of that helper too.
| Class | Parent | Interfaces |
|---|---|---|
SplDoublyLinkedList | — | Iterator, Countable, ArrayAccess |
SplStack | SplDoublyLinkedList | inherited from parent |
SplQueue | SplDoublyLinkedList | inherited from parent |
SplFixedArray | — | IteratorAggregate, ArrayAccess, Countable, JsonSerializable |
EmptyIterator | — | Iterator |
InternalIterator | — | Iterator |
ArrayIterator | — | Iterator, ArrayAccess, SeekableIterator, Countable |
ArrayObject | — | IteratorAggregate, ArrayAccess, Countable |
IteratorIterator | — | OuterIterator |
LimitIterator | IteratorIterator | inherited from parent |
NoRewindIterator | IteratorIterator | inherited from parent |
InfiniteIterator | IteratorIterator | inherited from parent |
FilterIterator | IteratorIterator | inherited from parent |
CallbackFilterIterator | FilterIterator | inherited from parent |
CachingIterator | IteratorIterator | ArrayAccess, Countable, Stringable |
AppendIterator | IteratorIterator | inherited from parent |
MultipleIterator | — | Iterator |
RecursiveArrayIterator | ArrayIterator | RecursiveIterator |
RecursiveFilterIterator | FilterIterator | RecursiveIterator |
RecursiveCallbackFilterIterator | CallbackFilterIterator | RecursiveIterator |
RecursiveIteratorIterator | — | OuterIterator |
ParentIterator | RecursiveFilterIterator | inherited from parent |
See SPL for the supported method surface, iterator modes, examples, and current compatibility gaps.
Type checks with instanceof
<?php
interface Renderable {
public function render();
}
class Widget {
public function render() { return "widget"; }
}
class Button extends Widget implements Renderable {}
$item = new Button();
echo ($item instanceof Button) ? "yes" : "no"; // yes
echo ($item instanceof Widget) ? "yes" : "no"; // yes
echo ($item instanceof Renderable) ? "yes" : "no"; // yes
$target = "Button";
echo ($item instanceof $target) ? "yes" : "no"; // yes
The runtime check uses emitted class metadata, so subclasses match parent classes and implemented interfaces. The left-hand side may be a direct object or a boxed mixed / nullable / union value; non-object payloads return false once any dynamic target has been validated. Supported targets are named classes/interfaces, self, parent, late-bound static, dynamic class/interface strings, and dynamic object expressions.
Abstract classes
<?php
abstract class BaseGreeter {
abstract public function label();
public function greet() { return "hi " . $this->label(); }
}
- cannot be instantiated
- abstract methods must be bodyless
- non-abstract classes may not have abstract methods
Abstract properties
An abstract class may declare a PHP 8.4 hooked property contract as abstract. The declaration has no default value or hook body, and every concrete subclass must redeclare the property with a compatible public/protected property. Static, final, private, and readonly hooked abstract properties are rejected.
<?php
abstract class Shape {
abstract public int $sides { get; set; }
}
class Square extends Shape {
public int $sides = 4;
}
The concrete redeclaration reuses the parent’s slot (offsets are stable across the inheritance chain), so the property is accessible to both parent and child methods. elephc supports hook contracts ({ get; }, { set; }, and { get; set; }) in abstract classes, interfaces, and traits, and executable hook bodies on concrete properties (see Property hooks).
Final classes, methods, and properties
<?php
final class InvoiceNumber {
final public $value = 42;
final public function label() {
return "invoice:" . $this->value;
}
}
final classcannot be extendedfinalmethods cannot be overridden by subclassesfinalproperties cannot be redeclared by subclassesfinaldoes not change object layout or dispatch for normal callsabstract finalclasses and methods are rejectedfinal privatemethods emit a warning, matching PHP, because private methods are not overridden;__constructis the exceptionfinal privateproperties are rejected, matching PHP
Properties
public,protected,privatevisibility- Optional default values
- Optional type declarations, for example
public int $idorpublic ?string $email = null readonlyproperties (only assigned in__construct)finalproperties, which can be read normally but cannot be redeclared by subclasses- Static properties with
public static,protected static, orprivate static, including typed static properties readonly classmakes all instance properties readonly; static properties stay mutable
Statically-known access violations — calling a private/protected method from an inaccessible scope, or writing a readonly property outside its declaring constructor — raise a catchable Error exception at runtime, matching PHP. Without a try/catch handler the exception is a fatal uncaught exit.
<?php
class User {
public int $id;
public string $name = "Ada";
public ?string $email = null;
public function __construct($id) {
$this->id = $id;
}
}
Property type declarations are checked at compile time for both instance and static properties. Defaults and later assignments must be compatible with the declared type, including constructor assignments through untyped parameters. Typed properties without an explicit default start in PHP’s uninitialized state; reading an instance or static property before the first assignment is a fatal runtime error, while assigning values such as 0, false, "", or null to compatible nullable storage initializes the slot normally. Nullable shorthand (?T) and union storage use the compiler’s boxed mixed representation internally. void and callable property types are rejected.
An untyped property with no explicit default (public $x;, instance or static) is initialized to null, exactly like public $x = null; — matching PHP, where untyped properties are implicitly nullable. When later assignments give such a property a concrete scalar or array type, the slot keeps nullable storage (the same layout as a typed ?T property) so the null default stays observable before the first write; heterogeneous assignments widen the slot to mixed. Assignments made inside the class’s own constructor initialize the slot before any observable read, so those keep the precise inferred type.
Property default values are applied both for the normal new ClassName() form and for dynamic new $variable() instantiation (and therefore for runtime-instantiated stream wrappers and stream filters). When the class name resolves to a known class, dynamic instantiation follows the same allocation path as direct construction, so constructor arguments are evaluated and __construct runs normally.
An array-typed (or untyped) property may take an associative literal default such as ['a' => 1]. The property is then stored as an associative array, so string-key reads and writes ($this->data['a'], $this->data[$key]) type-check and run like any other associative array. A positional literal default ([1, 2, 3]) keeps integer-keyed list storage.
<?php
class Bag {
public array $data = ['a' => 1, 'b' => 2];
public function get(string $key): int {
return $this->data[$key] ?? 0;
}
}
echo (new Bag())->get('a'); // 1
Asymmetric visibility (private(set))
PHP 8.4 asymmetric visibility lets a property be read more widely than it can be written. A (set) modifier after a visibility keyword sets the write visibility independently of the read visibility:
<?php
class Counter {
public private(set) int $value = 0; // read: public, write: private
public function increment(): void {
$this->value = $this->value + 1; // allowed: write from inside the class
}
}
$c = new Counter();
$c->increment();
echo $c->value; // 2 — public read
// $c->value = 9; // rejected: write is private
Rules:
- The
setvisibility applies to writes; the ordinary (read) visibility applies to reads. - A lone
private(set)/protected(set)leaves the read visibility at itspublicdefault. protected(set)allows writes from the declaring class and its subclasses;private(set)only from the declaring class.- The write visibility must not be weaker than the read visibility (
private public(set)is rejected). - The property must be typed, and the modifier is not allowed on static properties.
- Indirect writes through an array element (
$obj->items[] = x,$obj->items['k'] = x) are writes too, so they honor thesetvisibility — not the (wider) read visibility. - Abstract and interface property hook contracts may carry asymmetric write visibility on writable (
{ set; }) contracts.private(set)contracts are final and cannot be implemented or redeclared by a concrete child property.
Property redeclaration
A child class may redeclare a property inherited from a non-private parent. The redeclaration is checked at compile time and must follow PHP rules:
- Visibility cannot be reduced (
public→protectedis rejected;protected→publicis allowed). - Declared types are invariant. A typed parent property must be redeclared with the same type. A typed parent property cannot become untyped, and an untyped parent property cannot gain a type in the child.
readonlyis monotonic — areadonlyparent property must stayreadonlyin the child. A non-readonly parent property may becomereadonlyin the child.- The by-reference qualifier on a property cannot change across inheritance.
finalparent properties cannot be redeclared.- The child shares the parent’s slot, so reads of the property from inherited methods see the child’s value.
Private parent properties are different: they are not overridden. A child may declare a same-named property, and elephc keeps a separate storage slot for each declaring class. Methods declared on the parent keep reading/writing the private parent slot, while child methods and external public access use the child property.
<?php
class Base {
private int $value = 1;
public function parentValue(): int {
return $this->value;
}
}
class Child extends Base {
public int $value = 5;
}
$child = new Child();
echo $child->value . ":" . $child->parentValue(); // 5:1
Property hooks (get / set)
A property can define hooks that run when it is read or written, replacing hand-written getter and setter methods. The hooks live in a { ... } block after the property name. Reading the property runs its get hook; assigning to it runs its set hook.
A get-only property is virtual — it has no stored value of its own and is read-only. The short form get => expr; returns expr; the block form get { ... } runs statements and returns a value:
<?php
class Person {
public string $first = "Ada";
public string $last = "Lovelace";
public string $full {
get => $this->first . " " . $this->last;
}
}
echo (new Person())->full; // Ada Lovelace
A property with both get and set typically reads from and writes to a separate backing field. The value assigned to the property is available inside the set hook as $value (rename it with set(Type $other)):
<?php
class Thermostat {
private float $c = 0.0;
public float $celsius {
get => $this->c;
set { $this->c = $value; }
}
public float $fahrenheit {
get => $this->c * 9.0 / 5.0 + 32.0;
set { $this->c = ($value - 32.0) * 5.0 / 9.0; }
}
}
$t = new Thermostat();
$t->fahrenheit = 212.0;
echo $t->celsius; // 100
The short set => expr; form stores expr into the property’s own backing slot:
<?php
class Name {
public string $value {
get => $this->value;
set => trim($value);
}
}
Inside a property’s own hook, $this->prop accesses the raw stored value rather than re-running the hook, so a hook may read and write the property it belongs to (a backed property):
<?php
class Name {
public string $value {
get => $this->value;
set { $this->value = trim($value); }
}
}
$n = new Name();
$n->value = " Ada ";
echo $n->value; // Ada
Rules:
- A property with a
gethook but nosethook is read-only. Writing it is a compile-time error. - Hooked properties cannot have a default value and cannot be
static,final, orreadonly. - Each hook may be declared at most once per property.
set => expr;is equivalent to assigningexprto the property’s own backing slot.- Hooks are inherited by subclasses along with the property.
Abstract classes, interfaces, and traits may declare hook contracts ({ get; }, { set; }, { get; set; }) with no body; see Abstract properties and Interfaces.
Static properties
Static properties use class-scoped storage and are accessed with ::.
<?php
class Counter {
public static int $count = 1;
public static function bump() {
self::$count = self::$count + 1;
return self::$count;
}
}
echo Counter::$count; // 1
Counter::$count = 5;
echo Counter::bump(); // 6
Supported receivers are ClassName::$prop, self::$prop, parent::$prop, and static::$prop. Static property visibility and declared types are checked at compile time. Typed static properties without defaults use the same uninitialized-read fatal as typed instance properties. Inherited static properties share the declaring class storage until a subclass redeclares the property. Redeclarations follow PHP rules: non-private inherited properties keep invariant declared types, cannot reduce visibility, and cannot override final properties. Private static properties redeclared in subclasses are independent slots; static::$prop is still late-bound and reports a fatal runtime error if the current method scope cannot access the matched private slot.
Static properties in elephc, like in PHP, are always mutable — even on a readonly class. PHP’s readonly modifier only constrains instance properties; declaring public readonly static is a compile error in both PHP and elephc.
Static array properties support direct element writes:
<?php
class Registry {
public static array $items = [];
}
Registry::$items[] = 10;
Registry::$items[0] = 12;
echo Registry::$items[0]; // 12
Constructor
Called automatically with new:
$p = new Point(3, 4);
Constructor property promotion is supported. Visibility or readonly before a constructor parameter declares a property and assigns the incoming argument at the start of __construct.
<?php
class User {
public function __construct(
public int $id,
private string $name = "Ada",
readonly ?int $rank = null
) {}
public function name() {
return $this->name;
}
}
$user = new User(7);
echo $user->id; // 7
echo $user->name(); // Ada
Promoted properties support public, protected, private, readonly, nullable and union type declarations, constructor parameter defaults, and by-reference parameters. Variadic promotion is rejected, matching PHP.
By-reference promoted properties are supported when the constructor argument is a variable:
<?php
class Counter {
public function __construct(public int &$value) {}
}
$value = 1;
$counter = new Counter($value);
$value = 2;
echo $counter->value; // 2
$counter->value = 3;
echo $value; // 3
By-reference promoted parameters may also have defaults. If no argument is passed, elephc creates a private reference cell for the default value; if a variable is passed, the promoted property aliases that variable. readonly by-reference promoted properties are rejected at compile time because construction would have to bind an indirect mutable alias to a readonly slot.
Instance methods and $this
Virtual dispatch for overrides. Private methods are not virtual.
Nullsafe access
Use ?-> when a receiver may be null:
<?php
echo $user?->profile?->name ?? "anonymous";
echo $user?->profile?->label() ?? "missing";
echo $user?->profile->address?->city ?? "unknown";
$segment = "profile";
echo $user?->{$segment}?->name ?? "anonymous";
When a nullsafe receiver is null, elephc skips the rest of that postfix chain and returns null. This matches PHP for mixed chains such as $user?->profile->address: the ordinary ->address segment is skipped when $user is null, but still warns or fatals normally if $user is non-null and profile itself is null. Method arguments, array indexes, and callable arguments on the skipped branch are not evaluated.
parent::method()
Direct parent implementation call.
self::method()
Binds to lexical class, not runtime child.
static::method()
Late static binding — resolves against called class at runtime.
Static methods
Called with ::, no $this.
Class name reflection (::class)
::class returns a fully-qualified class name as a string. Named and scoped
receivers are resolved without constructing an object; an object-valued receiver
reads the concrete class from the object at runtime.
<?php
namespace App;
class Logger {
public static function tag() {
return self::class; // "App\Logger"
}
}
echo Logger::class; // "App\Logger"
echo \App\Logger::class; // "App\Logger"
$logger = new Logger("audit");
echo $logger::class; // "App\Logger"
Supported receivers: Class::class, \Vendor\Class::class, self::class,
parent::class, static::class, and statically known object-valued expressions
such as $object::class or make_object()::class.
static::class follows PHP late static binding and resolves to the called class.
$object::class follows the object’s concrete runtime class, including when its
static type is a parent class. The receiver expression is evaluated exactly once.
For named receivers, elephc preserves PHP’s written/imported spelling for the
::class string while still using case-insensitive class lookup for executable
operations such as new, instanceof, static method calls, and static property
access.
Late static binding constructors (new self(), new static(), new parent())
The new self(), new static(), and new parent() factory patterns are supported inside class methods:
<?php
class Box {
public string $label = "default";
public static function make(): Box {
return new self();
}
}
$b = Box::make();
echo $b->label; // "default"
class Base {
public string $kind = "base";
}
class Child extends Base {
public static function makeBase(): Base {
return new parent();
}
}
new static() follows PHP late static binding and constructs an instance of the called class.
Relative class types (self, static, parent)
self, static, and parent may be used as type declarations on method parameters, method return types, and properties. self and parent resolve lexically; a method return declared as static is late-bound to the call-site receiver:
<?php
class Money {
public function __construct(public int $amount) {}
// `self` return type: enables fluent chaining.
public function add(self $other): self {
return new Money($this->amount + $other->amount);
}
}
class Node {
public ?self $next = null; // a same-class (nullable) property
public function __construct(public int $value) {}
}
trait Fluent {
// In a trait, `static` stays late-bound within the using class hierarchy.
public function self(): static {
return $this;
}
}
Rules:
selfresolves to the class the member is declared in;parentresolves to that class’s parent.- A method return declared as
staticbinds to the receiver class or interface at each call site, including inherited methods and fluent chains. - Overrides and interface implementations must preserve a required late-bound
static; replacing it with the current concrete class is rejected. Covariant narrowing such asstatic|falsetostaticremains valid. - They are accepted in parameter, return, and property type positions, and may be combined with the nullable shorthand (
?self) or unions (self|null). - Used inside a trait,
selfresolves to the using class and a returnstaticremains late-bound within that class hierarchy. - Using
self,static, orparentas a type outside of a class is rejected. - PHPDoc return annotations are not parsed for typing. A fluent API that needs late-static refinement must declare the native PHP return type
: static;@return staticalone does not change the inferred type.
Dynamic instantiation (new $variable())
new $variable() constructs an instance whose class is selected at runtime from a string variable:
<?php
class Foo {}
class Bar {}
$cls = "Foo";
$obj = new $cls(); // Foo instance
echo gettype($obj); // "object"
$missing = "NoSuchClass";
new $missing(); // fatal: Class "NoSuchClass" not found
elephc resolves the class name case-insensitively against compile-time class metadata, matching PHP class lookup. A match dispatches through the same allocation path as new ClassName(), including constructor calls, declared property defaults, and supported built-in/SPL runtime storage initialization. Unknown class strings are fatal, and non-string class expressions are rejected with PHP’s invalid class-name fatal.
Dynamic method and static calls
A method or static method can be called by a name held in a variable:
<?php
class Calculator {
public function add(int $a, int $b): int { return $a + $b; }
public static function version(): string { return "1.0"; }
}
$calc = new Calculator();
$method = "add";
echo $calc->$method(2, 3); // 5 — dynamic instance method
echo $calc->{$method}(2, 3); // 5 — brace form
$class = "Calculator";
echo $class::version(); // 1.0 — dynamic static call
$static = "version";
echo $class::$static(); // 1.0 — both class and method dynamic
$obj->$name(...) and $class::$name(...) are equivalent to call_user_func([$obj, $name], ...) / call_user_func([$class, $name], ...). A dynamic method name on a literal class also works (ClassName::$name(...)). Arguments are forwarded positionally. Nullsafe dynamic method calls ($obj?->$name(...) and $obj?->{$expr}(...)) short-circuit like other ?-> chains: if the receiver is null, the method-name expression and arguments are not evaluated and the result is null. Named arguments are rejected in dynamic calls because the target method — and therefore its parameter names — is not known at compile time.
Anonymous classes (new class {})
An anonymous class defines and instantiates a class in one expression. It may take constructor arguments, extend a class, and implement interfaces:
<?php
interface Logger {
public function log(string $message): string;
}
function make_logger(string $prefix): Logger {
return new class($prefix) implements Logger {
public function __construct(private string $prefix) {}
public function log(string $message): string {
return $this->prefix . ": " . $message;
}
};
}
echo make_logger("INFO")->log("started"); // INFO: started
Rules:
- Constructor arguments go before any
extends/implementsclause:new class(args) extends P implements I { ... }. - Each anonymous class is compiled to its own uniquely-named class, so two
new class {}expressions produce two independent types. - Like a named class, an anonymous class does not capture variables from the enclosing scope; pass data in through the constructor.
new readonly class { ... }is supported.
Override rules
Same parameter count, same pass-by-reference positions, same default layout, same variadic shape.
Traits
Flattened at compile time. Support: use Trait;, multiple traits, insteadof, as, trait properties, static trait methods.
Traits may declare abstract hooked property contracts. A concrete class using the trait must satisfy the contract directly or inherit it through an abstract base class that is later completed by a concrete child.
Property access
-> for properties and methods.
Dynamic property access
The property name can be computed at runtime with the $obj->{$expr} syntax,
where $expr is any expression that evaluates to a string. The same form works
as an assignment target and combines with the nullsafe operator ($obj?->{$expr}).
<?php
class Point {
public int $x = 1;
public int $y = 2;
}
$p = new Point();
$field = "x";
echo $p->{$field}; // 1
$p->{$field} = 9;
echo $p->x; // 9
Enums
<?php
enum Color: int {
case Red = 1;
case Green = 2;
}
echo Color::Red->name; // Red
echo Color::Red->value; // 1
echo Color::from(2) === Color::Green; // 1
Pure and backed enums. Every case exposes the read-only ->name property (the case identifier); backed cases also expose ->value. Case names are case-sensitive and may use PHP keywords other than the reserved class name; their exact declaration spelling is retained, so case Match and case MATCH are distinct and report Match / MATCH through ->name. Plus ::from(), ::tryFrom(), ::cases(). Only int and string backing types.
Like PHP, an int-backed enum’s ::from() / ::tryFrom() accept a numeric string and coerce it to the integer backing value (Color::from("2") returns Color::Green). A numeric string with no matching case throws ValueError; a non-numeric string (e.g. "x", "1abc", "0x1", "INF", or "NAN") throws TypeError, matching PHP’s coercive typing.
A dynamically-typed (mixed) argument — such as a foreach value or an untyped parameter — is also accepted and coerced on its runtime type: an integer or numeric string resolves (or throws ValueError), a float truncates, a bool/null coerces, and an array/object/resource/closure throws TypeError naming the given type (objects report object rather than the class name).
Enum methods, constants, and interfaces
Enums may declare instance methods, static methods, constants, and an implements clause. Instance methods dispatch on the case singleton, so $this is the case:
<?php
interface HasLabel {
public function label(): string;
}
enum Suit: string implements HasLabel {
case Hearts = "H";
case Spades = "S";
const COUNT = 2;
public function label(): string {
return match ($this) {
Suit::Hearts => "Hearts",
Suit::Spades => "Spades",
};
}
public function code(): string {
return $this->value; // backing value
}
public static function default(): self {
return Suit::Hearts; // static factory
}
}
echo Suit::Hearts->label(); // Hearts
echo Suit::default()->code(); // H
Rules:
- Instance methods may use
$this(the case),match ($this), the case$this->name, the backing$this->value, andself::CONST. - Static methods dispatch like class static methods and can act as factories.
- An enum can
implementsone or more interfaces and be used through them. - Enums can
usetraits that provide methods, includinginsteadofandasadaptations. Traits with properties are rejected because PHP enums cannot have properties. self/statictype hints in enum methods resolve to the enum.
Enum constants are readable both inside the enum (self::CONST) and from outside it (EnumName::CONST). Enum method bodies are type-checked like class method bodies, so a mismatched return type or an undefined variable inside an enum method is reported.
Built-in SortDirection
PHP 8.6’s global unit enum is available without a user declaration:
<?php
function sqlSortKeyword(SortDirection $direction): string {
return match ($direction) {
SortDirection::Ascending => "ASC",
SortDirection::Descending => "DESC",
};
}
echo sqlSortKeyword(SortDirection::Descending); // DESC
SortDirection has two cases, Ascending and Descending, no backing value, and works with enum case identity, SortDirection::cases(), enum_exists(), type declarations, match, imports, and fully-qualified \SortDirection references.
Magic methods
__construct(...)— runs at instantiation__destruct()— runs when the object is released (see below)__clone()— runs afterclone $objectcreates the shallow copy__toString()— string coercion__get($name)— reading an undeclared property__set($name, $value)— writing an undeclared property__isset($name)—isset()/empty()on an undeclared or inaccessible property__unset($name)—unset()of an undeclared or inaccessible property__invoke(...$args)— calling an object directly__call($name, $args)— intercepting missing instance methods__callStatic($name, $args)— intercepting missing static methods
Property interception (__get, __set, __isset, __unset)
When code reads, writes, tests, or removes a property that the class does not declare, the matching magic method is invoked. This lets a class expose virtual properties backed by any internal representation:
<?php
class Config
{
private bool $debugEnabled = true;
public function __get(string $name): bool
{
return $name === "debug" && $this->debugEnabled;
}
public function __isset(string $name): bool
{
return $name === "debug" && $this->debugEnabled;
}
public function __unset(string $name): void
{
if ($name === "debug") {
$this->debugEnabled = false;
}
}
}
$config = new Config();
echo isset($config->debug) ? "on" : "off"; // on → __isset
unset($config->debug); // → __unset
echo isset($config->debug) ? "on" : "off"; // off → __isset
isset($obj->prop) returns the boolean result of __isset; unset($obj->prop)
runs __unset for its side effects. Both fire only for properties the class does
not declare or cannot access from the calling scope — a property the caller can
see is read, tested, and removed directly, exactly as in PHP.
Contract: __isset and __unset must be non-static and public, and each takes
exactly one argument (the property name). __isset returns bool.
unset() on a declared property
unset($obj->prop) on a property the caller can see removes it from the instance
without consulting __unset. For a typed property PHP returns the slot to its
uninitialized state, and elephc reproduces that exactly:
<?php
class Row { public int $id = 3; public string $label = "a"; }
$row = new Row();
unset($row->id, $row->label);
var_dump(isset($row->id)); // false
print_r($row); // Row Object ( ) — both properties are gone
echo $row->id; // Error: Typed property Row::$id must not be
// accessed before initialization
$row->id = 9; // assigning again brings the property back
var_dump(isset($row->id)); // true
isset() on a typed property that was never initialized (or was unset) answers
false without raising the uninitialized-read error, matching PHP. The same
suppression applies to empty(), ??, and ??=: $row->id ?? "none" answers
the default instead of raising, on instance and static typed properties alike,
including reads through a nullable receiver (?Row $r → $r->id ?? "none"). A
plain read still raises the catchable Error.
unset() on a dynamic property
A property that lives in the object’s dynamic-property hash — every stdClass
property, and any undeclared name on an #[AllowDynamicProperties] class — is
genuinely removable, so unset() matches PHP exactly: the key disappears, isset()
answers false, the value renderers stop listing it, unsetting the same name twice
or unsetting a name that was never set are both no-ops, and a later write recreates
the property at the end of the property order.
<?php
$o = new stdClass();
$o->a = 1;
$o->b = "two";
unset($o->a);
var_dump(isset($o->a)); // false
echo json_encode($o); // {"b":"two"}
unset($o->a); // no-op
unset($o->never_set); // no-op
$o->a = 9;
echo json_encode($o); // {"b":"two","a":9}
Declared slots on an #[AllowDynamicProperties] class keep the fixed-slot rules
above; only undeclared names go through the hash.
One shape is refused here: a class that declares __unset() and allows dynamic
properties. PHP calls __unset() only when the dynamic name is absent at the unset
site and removes the entry silently when it is present, so the choice depends on
runtime state that elephc resolves statically. It reports an unsupported-feature
diagnostic instead of guessing.
unset() limitations
unset() on an untyped declared property (public $foo = 1;) is rejected at
compile time with an unsupported-feature diagnostic naming that shape. PHP truly
removes such a property: a later read emits Warning: Undefined property: C::$foo
and answers null, and a later write recreates it. elephc gives every declared
property a fixed, monomorphically typed slot — public $foo = 1; is stored as an
int — and that slot has no encoding for “removed, and reading as null”: every
candidate encoding answers int(0) or a raw marker word instead. A loud compile
error beats a wrong value. Declare a type (public mixed $foo = 1; keeps the
“anything goes” storage) when the property needs to be unset, or make the property
dynamic. Note that the typed form follows PHP’s typed rules afterwards: the slot
becomes uninitialized, so a later read raises Error: Typed property … must not be accessed before initialization instead of warning and answering null.
unset() on a by-reference property (public function __construct(public int &$p) {}) is rejected for the same reason: the slot holds an object-owned reference
cell that the destructor still frees and that a later write would write through,
which would revive the very alias unset() is supposed to break.
Static call interception (__callStatic)
__callStatic is the static counterpart of __call: a static call to a method
the class does not define is forwarded to __callStatic, which receives the
called method name and an array of the arguments. Subclasses inherit it.
<?php
abstract class Query
{
public static function __callStatic(string $method, array $args): string
{
return $method . "(" . implode(", ", $args) . ")";
}
}
class User extends Query {}
echo User::where("active", "1"); // where(active, 1) → __callStatic
echo User::orderBy("name"); // orderBy(name) → __callStatic
Contract: __callStatic must be declared public static and takes exactly two
arguments — the method name (string) and the argument list (array).
Object cloning (clone)
clone $object creates a shallow copy of a user object or stdClass instance:
declared property slots are copied into a new object, dynamic properties are
copied into a separate hash table, and object-valued properties still point at
the same nested object just like PHP.
<?php
class Child {
public int $x = 1;
}
class Boxed {
public Child $child;
public function __construct() {
$this->child = new Child();
}
public function __clone(): void {
// Runs on the copy after the shallow copy has been created.
$this->child->x = $this->child->x + 1;
}
}
$a = new Boxed();
$b = clone $a;
__clone must be non-static, take no arguments, and if it declares a return
type the return type must be void. PHP permits any visibility for __clone;
elephc checks that the clone expression is allowed to access the hook from the
current scope. A private __clone can therefore be used from inside the
declaring class, while cloning that object from unrelated global code is
rejected.
Runtime-managed built-in objects whose storage is not represented as ordinary
declared properties are not cloneable yet. This includes Fiber, Generator,
Reflection objects, and SPL containers/iterators with native storage such as
SplFixedArray, SplDoublyLinkedList, SplStack, SplQueue,
IteratorIterator, CallbackFilterIterator, and
RecursiveCallbackFilterIterator.
Destructors (__destruct)
A class may declare public function __destruct(): void to run cleanup when an
object is released. elephc uses reference counting, so the destructor runs as
soon as the last reference goes away — there is no separate garbage-collection
delay. It fires when:
- a local variable holding the object goes out of scope (including on early
returnand during exception unwinding); - the variable is reassigned or
unset(), releasing the previous object; - the object was the last element keeping a containing array or object alive;
- the program ends, for objects still referenced by top-level variables.
The destructor runs before the object’s properties are released, so it can
still read $this and its properties. This makes RAII-style cleanup natural:
acquire a resource in __construct, release it in __destruct.
<?php
class TempFile
{
private string $path;
public function __construct(string $path)
{
$this->path = $path;
file_put_contents($this->path, "scratch");
}
public function __destruct()
{
unlink($this->path); // runs automatically when the object is released
}
}
Rules and notes:
__destructmust be non-static and take no arguments. Any visibility is allowed (PHP calls it regardless), so it may bepublic,protected, orprivate.- A subclass without its own
__destructinherits its parent’s. - Taking a temporary copy of
$thisinside the destructor is safe; the object is not freed twice. - Objects that form a reference cycle (e.g.
$a->next = $b; $b->next = $a;) are reclaimed by the targeted cycle collector, and their destructors still run. The collector is eager — it reclaims an unreachable island as soon as the last outside reference drops — so for cyclic objects the destructor timing can differ from PHP’s deferred cycle collector, and the order among the cycle’s members is unspecified. - Object resurrection is not supported: storing
$thissomewhere that outlives the destructor (so the object would survive) does not keep it alive — the object is still freed once the destructor returns. Avoid retaining$thispast the end of__destruct.
Attributes
PHP 8.0 attributes (#[Name]) decorate declarations. elephc parses attributes at every site PHP allows: classes, interfaces, traits, enums, enum cases, top-level functions, methods, properties, function/method/closure parameters (incl. promoted constructor params), closures, and arrow functions. Class, function, method, property, and method-parameter attributes have limited runtime reflection through the helpers below; attributes on other declaration sites are currently validated for syntax and kept only in the AST.
<?php
#[Author("Ada"), Version(1)]
class Greeter {
#[Slot]
public string $who;
public function __construct(#[Required] string $who) {
$this->who = $who;
}
#[Pure]
public function greet(): void { echo "Hello"; }
}
class LoudGreeter extends Greeter {
#[\Override]
public function greet(): void { echo "HELLO"; }
}
$pure = #[Pure] fn (int $x) => $x + 1;
#[Memoized]
function double(int $x): int { return $x * 2; }
Supported syntax:
- single attribute:
#[Foo] - attribute with arguments:
#[Bar(1, "two")] - named attribute arguments:
#[Bar(path: "/home", secure: true)] - multiple attributes per group:
#[A, B(1)] - stacked groups:
#[A] #[B] - fully-qualified names:
#[\Symfony\Contracts\Service\Attribute\Required]
# outside an attribute group introduces a PHP-style line comment, identical to //. Attributes before non-declaration statements (echo, if, assignments) are rejected — PHP’s strict rule.
Compile-time enforced attributes
#[\Override](PHP 8.3) — the type checker verifies that the marked method actually overrides a method declared in a parent class or implemented interface (transitively). A typo in the method name or a missing parent method becomes a compile-time error:<class>::<method>() has #[\Override] attribute, but no matching parent method was found. Both the unqualified#[Override]and fully-qualified#[\Override]forms are recognized.#[\Deprecated]/#[\Deprecated("reason")](PHP 8.4) — calls to the marked function, method, or static method emit a compile warning:Call to deprecated function: name() — reason. The reason argument (if a string literal) is appended to the message.#[\AllowDynamicProperties](PHP 8.2) — instances of the marked class accept assignment of undeclared properties at runtime. Each instance carries a per-object hashtable side-table allocated by the constructor (~296 bytes); the type checker accepts undeclared reads asmixed. The hashtable is freed automatically with the object.
Built-in attributes follow PHP class-name resolution. In a namespace, #[Deprecated] means #[CurrentNamespace\Deprecated]; use #[\Deprecated] or an import alias such as use Deprecated as Old; #[Old] to target the global built-in attribute.
<?php
#[\AllowDynamicProperties]
class Bag {
public int $declared = 1;
}
$b = new Bag();
$b->extra = 42; // accepted, stored in side-table
$b->name = "elephc"; // heterogeneous values supported
echo $b->declared; // 1
echo $b->extra; // 42
echo $b->name; // "elephc"
echo $b->missing; // empty (Mixed null)
User-defined attributes (e.g. #[Author], #[Pure], #[Memoized]) parse and persist in the AST. They have no compile-time semantics, but their names and literal positional or named arguments are reachable at runtime through lightweight helper builtins and the supported Reflection API:
<?php
#[Author("Ada"), Version(1)]
class Greeter {}
#[\Override]
class Solo {}
#[Route(path: "/api/users", method: "GET", secure: true)]
class UserController {}
foreach (class_attribute_names('Greeter') as $name) {
echo $name, "\n";
}
// Author
// Version
echo class_attribute_names('Solo')[0]; // "Override" (resolved name)
foreach (class_attribute_args('UserController', 'Route') as $key => $arg) {
echo $key, "=", $arg, "\n";
}
// path=/api/users
// method=GET
// secure=1 ← `true` echoes as 1 in PHP
class_attribute_args() returns an array<int|string, mixed>: positional arguments keep integer keys, named arguments keep their PHP argument names as string keys, and values preserve their original PHP type — strings stay strings, ints stay ints, floats stay floats, booleans stay booleans, null is null, and supported nested arrays stay arrays. The args are interned at compile time and boxed into mixed cells on demand at the call site.
For a more PHP-idiomatic API, class_get_attributes() and ReflectionClass::getAttributes() return the same data wrapped as ReflectionAttribute instances:
<?php
#[Author("Ada", 1815), Version("1.0", true)]
class Greeter {}
foreach (class_get_attributes('Greeter') as $attr) {
echo $attr->getName(), ": ";
foreach ($attr->getArguments() as $arg) {
echo "[", $arg, "]";
}
echo "\n";
}
// Author: [Ada][1815]
// Version: [1.0][1]
Reflection is also available for class members:
<?php
class Controller {
#[Route("/home", "GET")]
public function index() {}
#[Column("id")]
public int $id = 0;
}
$class = new ReflectionClass(Controller::class);
echo $class->getAttributes()[0]->getName();
$method = new ReflectionMethod('Controller', 'index');
echo $method->getAttributes()[0]->getArguments()[0]; // /home
$property = new ReflectionProperty('Controller', 'id');
echo $property->getAttributes()[0]->getName(); // Column
ReflectionAttribute is a final synthetic built-in class with getName(): string, getArguments(): array, and newInstance(): mixed methods. It is populated internally by class_get_attributes() and the supported Reflection lookups and cannot be constructed or populated directly from user code; its metadata slots are private. getArguments() returns the same array<int|string, mixed> shape as class_attribute_args(). newInstance() constructs the attribute class on demand when the attribute class exists in the program and the captured positional or named arguments are supported literals:
<?php
class Route {
public function __construct(string $path) {
echo $path;
}
}
#[Route("/lazy")]
class Controller {}
$attr = (new ReflectionClass('Controller'))->getAttributes()[0];
$instance = $attr->newInstance(); // constructor runs here
echo ($instance instanceof Route) ? "yes" : "no";
| Function | Signature | Description |
|---|---|---|
class_attribute_names() | class_attribute_names($class_name): array | Return the resolved attribute names decorating the class |
class_attribute_args() | class_attribute_args($class_name, $attribute_name): array | Return the supported literal positional/named arguments for the first matching class attribute |
class_get_attributes() | class_get_attributes($class_name): array | Return ReflectionAttribute objects for the class attributes |
| Reflection method | Supported constructor | Description |
|---|---|---|
ReflectionObject::* inherited class metadata and construction methods | new ReflectionObject($object) | Use the same reflected class metadata as ReflectionClass; construction helpers use the object’s runtime class id, straight-line receivers built from statically typed objects normalize named constructor arguments, and hasProperty() / getProperty() / getProperties() include public dynamic properties from the reflected instance |
ReflectionClass::getName() | new ReflectionClass($class_name) | Return the resolved class-like name |
ReflectionClass::getShortName() | new ReflectionClass($class_name) | Return the class-like name after the final namespace separator |
ReflectionClass::getNamespaceName() | new ReflectionClass($class_name) | Return the namespace portion of the reflected class-like name |
ReflectionClass::inNamespace() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is namespaced |
ReflectionClass::isFinal() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is final |
ReflectionClass::isAbstract() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is abstract |
ReflectionClass::isInterface() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is an interface |
ReflectionClass::isTrait() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is a trait |
ReflectionClass::isEnum() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is an enum |
ReflectionClass::isReadOnly() | new ReflectionClass($class_name) | Return whether the reflected class is a readonly class |
ReflectionClass::isAnonymous() | new ReflectionClass($class_name_or_object) | Return whether the reflected class is an anonymous class |
ReflectionClass::isInstantiable() | new ReflectionClass($class_name) | Return whether the reflected symbol is a concrete class with no constructor or a public constructor |
ReflectionClass::isCloneable() | new ReflectionClass($class_name) | Return whether the reflected symbol is a concrete cloneable class with no __clone() or a public __clone() |
ReflectionClass::isIterable() / isIterateable() | new ReflectionClass($class_name) | Return whether the reflected symbol is a concrete Iterator or IteratorAggregate class |
ReflectionClass::isInternal() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol is compiler-injected builtin metadata |
ReflectionClass::isUserDefined() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol comes from user source |
ReflectionClass::getModifiers() | new ReflectionClass($class_name) | Return PHP’s ReflectionClass::IS_* modifier bitmask for final, explicit abstract, and readonly class-like metadata |
ReflectionClass::hasMethod() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol has the named method; method lookup is case-insensitive |
ReflectionClass::hasProperty() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol has the named property; property lookup is case-sensitive |
ReflectionClass::hasConstant() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol has the named class constant or enum case; lookup is case-sensitive |
ReflectionClass::getConstant() | new ReflectionClass($class_name) | Return the reflected constant value, enum case object, or false when no such constant is visible |
ReflectionClass::getConstants() | new ReflectionClass($class_name) | Return an associative array of visible constant values keyed by constant or enum-case name |
ReflectionClass::getDefaultProperties() | new ReflectionClass($class_name) | Return an associative array of supported materialized instance/static property defaults keyed by property name |
ReflectionClass::getStaticProperties() | new ReflectionClass($class_name) | Return an associative array of supported materialized static property values keyed by property name |
ReflectionClass::getStaticPropertyValue() | new ReflectionClass($class_name) | Return a supported static property value, return the explicit default when the property is missing, or throw ReflectionException for supported missing-property lookups without a default |
ReflectionClass::setStaticPropertyValue() | new ReflectionClass($class_name) | Write a supported static property value |
ReflectionClass::getReflectionConstant() | new ReflectionClass($class_name) | Return a ReflectionClassConstant object for the named visible constant or enum case, or false when no such constant is visible |
ReflectionClass::getReflectionConstants() | new ReflectionClass($class_name) | Return indexed ReflectionClassConstant objects for visible class constants and enum cases |
ReflectionClass::implementsInterface() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol implements, extends, or is the requested interface; interface lookup is case-insensitive and invalid names throw ReflectionException |
ReflectionClass::isSubclassOf() | new ReflectionClass($class_name) | Return whether the reflected class-like symbol extends the requested class or implements/extends the requested interface; self is excluded, trait/enum targets return false, and missing names throw ReflectionException |
ReflectionClass::isInstance() | new ReflectionClass($class_name) | Return whether an object is an instance of the reflected class-like symbol, including parent-class, interface, and enum-case relations; trait targets return false |
ReflectionClass::getInterfaceNames() | new ReflectionClass($class_name) | Return implemented interface names for classes or parent interface names for interfaces |
ReflectionClass::getInterfaces() | new ReflectionClass($class_name) | Return implemented or parent interfaces as name-keyed ReflectionClass objects |
ReflectionClass::getTraitNames() | new ReflectionClass($class_name) | Return trait names used directly by classes or traits |
ReflectionClass::getTraits() | new ReflectionClass($class_name) | Return directly used traits as name-keyed ReflectionClass objects |
ReflectionClass::getTraitAliases() | new ReflectionClass($class_name) | Return direct trait method aliases as an alias-name to Trait::method map |
ReflectionClass::getMethods() | new ReflectionClass($class_name) | Return ReflectionMethod objects for methods visible through the reflected class-like metadata |
ReflectionClass::getConstructor() | new ReflectionClass($class_name) | Return the reflected constructor as a ReflectionMethod, or null when no constructor is visible |
ReflectionClass::getParentClass() | new ReflectionClass($class_name) | Return the reflected parent class as a ReflectionClass, or false when the reflected class-like symbol has no parent class |
ReflectionClass::getProperties() | new ReflectionClass($class_name) | Return ReflectionProperty objects for properties visible through the reflected class-like metadata |
ReflectionClass::newInstance() | new ReflectionClass($class_name) | Construct an instance of the reflected class with forwarded constructor arguments |
ReflectionClass::newInstanceArgs() | new ReflectionClass($class_name) | Construct an instance of the reflected class from an argument array |
ReflectionClass::newInstanceWithoutConstructor() | new ReflectionClass($class_name) | Allocate an instance of the reflected class without running __construct() |
ReflectionClass::getAttributes() | new ReflectionClass($class_name) | Return ReflectionAttribute objects for class attributes |
ReflectionObject::* inherited class metadata methods | new ReflectionObject($object) | Return the same reflected class metadata as ReflectionClass, with the reflected class taken from the object’s runtime class id |
ReflectionEnum::isBacked() / getBackingType() | new ReflectionEnum($enum_name) | Return whether the reflected enum is backed and expose int/string backing metadata as ReflectionNamedType |
ReflectionEnum::hasCase() / getCase() / getCases() | Eval-backed new ReflectionEnum($enum_name) | Return enum-case presence and ReflectionEnumUnitCase / ReflectionEnumBackedCase objects for eval-declared enum cases |
ReflectionFunction::getName() | new ReflectionFunction($function_name) | Return the canonical user or supported callable-builtin function name |
ReflectionFunction::getShortName() / getNamespaceName() / inNamespace() | new ReflectionFunction($function_name) | Return namespace-aware name metadata for the reflected user or supported callable-builtin function |
ReflectionFunction::isInternal() / isUserDefined() | new ReflectionFunction($function_name) | Return origin predicates for supported reflected functions |
ReflectionFunction::isClosure() / isDeprecated() / returnsReference() / isGenerator() | new ReflectionFunction($function_name) | Return retained function predicates; AOT reflection reports false for closures and return-by-reference, uses #[Deprecated] metadata, and reports generator functions from lowered generator flags |
ReflectionFunction::hasTentativeReturnType() / getTentativeReturnType() / isDisabled() | new ReflectionFunction($function_name) | Return PHP-compatible defaults for supported functions: no tentative return type and not disabled |
ReflectionFunction::getAttributes() | new ReflectionFunction($function_name) | Return ReflectionAttribute objects for function attributes |
ReflectionFunction::getParameters() | new ReflectionFunction($function_name) | Return ReflectionParameter objects for the reflected function parameters |
ReflectionFunction::getNumberOfParameters() | new ReflectionFunction($function_name) | Return the total number of reflected function parameters |
ReflectionFunction::getNumberOfRequiredParameters() | new ReflectionFunction($function_name) | Return the number of required reflected function parameters |
ReflectionFunction::hasReturnType() / getReturnType() | new ReflectionFunction($function_name) | Return retained declared named, nullable, union, void, or never return type metadata as ReflectionNamedType, ReflectionUnionType, or null when no supported return type is declared |
ReflectionFunction::isVariadic() | new ReflectionFunction($function_name) | Return whether the reflected function declares a variadic parameter |
ReflectionFunction::getClosureUsedVariables() | new ReflectionFunction($function_name) | Return an empty array for supported non-closure function reflectors |
ReflectionFunction::invoke() / invokeArgs() | new ReflectionFunction($function_name) | Invoke eval-declared reflected functions with forwarded named/default/variadic arguments; inline/tracked generated/AOT functions with declared or inferred/untyped parameter contracts and supported callable builtins are also lowered |
ReflectionMethod::getName() | new ReflectionMethod($class_name, $method_name) or deprecated new ReflectionMethod("ClassName::method") | Return the reflected method name |
ReflectionMethod::getShortName() / getNamespaceName() / inNamespace() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return PHP method-name metadata; methods report an empty namespace and false for inNamespace() |
ReflectionMethod::isInternal() / isUserDefined() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return origin predicates for supported reflected methods |
ReflectionMethod::isClosure() / isDeprecated() / returnsReference() / isGenerator() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return retained method predicates; AOT reflection reports false for closures and return-by-reference, uses #[Deprecated] metadata, and reports generator methods from lowered generator flags |
ReflectionMethod::hasTentativeReturnType() / getTentativeReturnType() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return PHP-compatible defaults for supported user methods: no tentative return type |
ReflectionMethod::hasPrototype() / getPrototype() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return retained parent/interface prototype metadata for supported reflected method overrides and interface implementations |
ReflectionMethod::getDeclaringClass() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Return a ReflectionClass object for the class-like symbol that declares the reflected method |
ReflectionMethod::getAttributes() | new ReflectionMethod($class_name, $method_name) | Return ReflectionAttribute objects for method attributes |
ReflectionMethod::isStatic() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is static |
ReflectionMethod::isPublic() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is public |
ReflectionMethod::isProtected() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is protected |
ReflectionMethod::isPrivate() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is private |
ReflectionMethod::isFinal() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is final |
ReflectionMethod::isAbstract() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is abstract |
ReflectionMethod::isConstructor() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getConstructor() | Return whether the reflected method is the constructor |
ReflectionMethod::isDestructor() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method is the destructor |
ReflectionMethod::getModifiers() | new ReflectionMethod($class_name, $method_name) | Return PHP’s ReflectionMethod::IS_* visibility/static/finality/abstract bitmask |
ReflectionMethod::setAccessible() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Accepted as a PHP-compatible no-op for eval-backed and generated/AOT method reflection |
ReflectionMethod::invoke() / invokeArgs() | new ReflectionMethod($class_name, $method_name) or ReflectionClass::getMethod() / getMethods() / getConstructor() | Invoke eval-declared reflected methods with PHP visibility-bypassing reflection semantics and forwarded named arguments; inline/tracked generated/AOT methods with declared or inferred/untyped parameter contracts are also lowered |
ReflectionMethod::getParameters() | new ReflectionMethod($class_name, $method_name) | Return ReflectionParameter objects for the reflected method parameters |
ReflectionMethod::getNumberOfParameters() | new ReflectionMethod($class_name, $method_name) | Return the total number of reflected method parameters |
ReflectionMethod::getNumberOfRequiredParameters() | new ReflectionMethod($class_name, $method_name) | Return the number of required reflected method parameters |
ReflectionMethod::hasReturnType() / getReturnType() | new ReflectionMethod($class_name, $method_name) | Return retained declared named, nullable, union, void, or never return type metadata as ReflectionNamedType, ReflectionUnionType, or null when no supported return type is declared |
ReflectionMethod::isVariadic() | new ReflectionMethod($class_name, $method_name) | Return whether the reflected method declares a variadic parameter |
ReflectionParameter::getName() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return the parameter name without $ |
ReflectionParameter::getPosition() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return the zero-based parameter position |
ReflectionParameter::isOptional() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return whether the parameter has a default value or is variadic |
ReflectionParameter::isVariadic() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return whether the parameter is variadic |
ReflectionParameter::isPassedByReference() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return whether the parameter is passed by reference |
ReflectionParameter::canBePassedByValue() | Same construction forms as ReflectionParameter::isPassedByReference() | Return whether the parameter is not by-reference |
ReflectionParameter::isPromoted() | Same construction forms as ReflectionParameter::isPassedByReference() | Return whether the parameter came from constructor property promotion |
ReflectionParameter::hasType() | new ReflectionParameter("function", "name" or 0), new ReflectionParameter([ClassLike::class, "method"], "name" or 0), new ReflectionParameter([$object_expr, "method"], "name" or 0) with a statically known object type, or ReflectionMethod::getParameters() | Return whether the parameter has a declared type |
ReflectionParameter::getType() | Same as ReflectionParameter::hasType() | Return a ReflectionNamedType for simple named parameter types, a ReflectionUnionType for multi-member union parameter types, a ReflectionIntersectionType for intersection parameter types, or null when no type is declared or the declared type needs a broader Reflection type object |
ReflectionParameter::getClass() | Same as ReflectionParameter::hasType() | Return a ReflectionClass object for nullable or non-nullable named object parameter types, or null for builtin, union, intersection, or untyped parameters |
ReflectionParameter::allowsNull() | Same as ReflectionParameter::hasType() | Return PHP nullability for retained parameter type/default metadata |
ReflectionParameter::isArray() / isCallable() | Same as ReflectionParameter::hasType() | Return PHP’s legacy named-type predicates for nullable or non-nullable array / callable parameter declarations; union declarations report false |
ReflectionParameter::getAttributes() | Same construction forms as ReflectionParameter::hasType() | Return ReflectionAttribute objects for function and method parameter attributes |
ReflectionParameter::getDeclaringClass() | Same construction forms as ReflectionParameter::hasType() | Return a ReflectionClass object for method parameters, or null for function parameters |
ReflectionParameter::getDeclaringFunction() | Same construction forms as ReflectionParameter::hasType() | Return a ReflectionMethod object for method parameters or a ReflectionFunction object for function parameters |
ReflectionParameter::isDefaultValueAvailable() | Same construction forms as ReflectionParameter::isOptional() | Return whether a reflected parameter has a materialized default value |
ReflectionParameter::getDefaultValue() | Same construction forms as ReflectionParameter::isDefaultValueAvailable() | Return supported scalar/null, class-constant, array literal, and object default values with up to eight supported scalar/null constructor arguments from literals or constants, including omitted constructor defaults, or throw ReflectionException when no default is available |
ReflectionParameter::isDefaultValueConstant() | Same construction forms as ReflectionParameter::isDefaultValueAvailable() | Return whether the retained default came from a named constant |
ReflectionParameter::getDefaultValueConstantName() | Same construction forms as ReflectionParameter::isDefaultValueAvailable() | Return the retained constant name when the default is constant-backed, null otherwise |
ReflectionNamedType::getName() / allowsNull() / isBuiltin() / __toString() | ReflectionParameter::getType() | Return and stringify simple parameter type metadata |
ReflectionUnionType::getTypes() / allowsNull() / __toString() | ReflectionParameter::getType() | Return non-null union members as ReflectionNamedType objects, report whether null was part of the union, and stringify retained union metadata |
ReflectionIntersectionType::getTypes() / allowsNull() / __toString() | ReflectionParameter::getType() | Return intersection members as ReflectionNamedType objects, report false for nullability, and stringify retained intersection metadata |
ReflectionProperty::getName() | new ReflectionProperty($class_name, $property_name) | Return the reflected property name |
ReflectionProperty::getDeclaringClass() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() / getProperties() | Return a ReflectionClass object for the class-like symbol that declares the reflected property |
ReflectionProperty::getAttributes() | new ReflectionProperty($class_name, $property_name) | Return ReflectionAttribute objects for property attributes |
ReflectionProperty::isStatic() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is static |
ReflectionProperty::isPublic() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is public |
ReflectionProperty::isProtected() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is protected |
ReflectionProperty::isPrivate() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is private |
ReflectionProperty::isFinal() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is final |
ReflectionProperty::isAbstract() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is abstract |
ReflectionProperty::isReadOnly() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property is readonly |
ReflectionProperty::isPromoted() | new ReflectionProperty($class_name, $property_name) | Return whether the reflected property came from constructor property promotion |
ReflectionProperty::isProtectedSet() | new ReflectionProperty($class_name, $property_name) | Return whether asymmetric write visibility is protected(set) |
ReflectionProperty::isPrivateSet() | new ReflectionProperty($class_name, $property_name) | Return whether asymmetric write visibility is private(set) |
ReflectionProperty::getModifiers() | new ReflectionProperty($class_name, $property_name) | Return PHP’s ReflectionProperty::IS_* visibility/static/finality/abstract/readonly/virtual/set-visibility bitmask |
ReflectionProperty::isDefault() | new ReflectionProperty($class_name, $property_name), new ReflectionProperty($object, $property_name), or ReflectionClass::getProperty() / getProperties() | Return whether the reflected property is declared/default rather than dynamic |
ReflectionProperty::isDynamic() | Same as ReflectionProperty::isDefault() | Return false for supported declared properties and true for public dynamic object properties materialized from an object argument |
ReflectionProperty::isLazy() | Same as ReflectionProperty::isDefault() plus an object argument | Return false for supported declared and dynamic properties because elephc does not implement lazy properties |
ReflectionProperty::skipLazyInitialization() | Same as ReflectionProperty::isLazy() | Accepted as a no-op for supported non-static, non-virtual declared properties |
ReflectionProperty::__toString() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() / getProperties() | Format retained generated/eval property metadata as a PHP-style Property [ ... ] descriptor |
ReflectionProperty::hasHooks() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() / getProperties() | Return whether the reflected property has retained get/set hook metadata |
ReflectionProperty::getHooks() | Same as ReflectionProperty::hasHooks() | Return a string-keyed map of retained concrete generated/AOT property hook ReflectionMethod objects |
ReflectionProperty::hasHook(PropertyHookType $type) | Same as ReflectionProperty::hasHooks() plus PropertyHookType::Get / PropertyHookType::Set | Return whether the reflected property exposes the requested get/set hook |
ReflectionProperty::getHook(PropertyHookType $type) | Same as ReflectionProperty::hasHook() | Return the requested hook ReflectionMethod, or null when that hook is not present |
ReflectionProperty::hasType() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() / getProperties() | Return whether the reflected property has a retained declared type |
ReflectionProperty::getType() | Same as ReflectionProperty::hasType() | Return a ReflectionNamedType, ReflectionUnionType, or ReflectionIntersectionType for supported property types, or null when no type is declared |
ReflectionProperty::getSettableType() | Same as ReflectionProperty::hasType() | Return the retained settable property type as a ReflectionNamedType, ReflectionUnionType, or ReflectionIntersectionType, or null when no type is declared; for the supported property surface this currently matches getType() |
ReflectionProperty::hasDefaultValue() | Same as ReflectionProperty::hasType() | Return whether the reflected property has a materialized default value; untyped concrete properties without an explicit initializer report PHP’s implicit null default |
ReflectionProperty::getDefaultValue() | Same as ReflectionProperty::hasDefaultValue() | Return supported materialized property defaults, or null when no default is available |
ReflectionProperty::isInitialized() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() for supported known properties; runtime-held static reflectors use the materialized declaring-class snapshot | Return whether a supported instance/static property slot is initialized without reading the property value |
ReflectionProperty::getValue() | ReflectionProperty for instance properties with an explicit object argument, inline new ReflectionProperty($class_name, $property_name) / ReflectionClass::getProperty() for known static properties, or runtime-held static reflectors backed by the declaring-class snapshot | Read supported instance/static property storage; positional and named arguments are normalized |
ReflectionProperty::setValue() | Instance reflectors with an explicit object/value argument, or inline/tracked known static property reflectors | Write supported instance/static property storage |
ReflectionProperty::setAccessible() | new ReflectionProperty($class_name, $property_name) or ReflectionClass::getProperty() / getProperties() | Accepted as a PHP-compatible no-op for eval-backed and generated/AOT property reflection |
ReflectionClassConstant::getName() | new ReflectionClassConstant($class_name, $constant_name) or ReflectionClass::getReflectionConstant() / getReflectionConstants() | Return the reflected class constant or enum-case name |
ReflectionClassConstant::getAttributes() | Same as ReflectionClassConstant::getName() | Return ReflectionAttribute objects for class-constant or enum-case attributes |
ReflectionClassConstant::getDeclaringClass() | Same as ReflectionClassConstant::getName() | Return a ReflectionClass object for the class-like symbol that declares the reflected constant or enum case |
ReflectionClassConstant::getValue() | Same as ReflectionClassConstant::getName() | Return the reflected class-constant value, or the enum-case object for reflected enum cases |
ReflectionClassConstant::isEnumCase() | Same as ReflectionClassConstant::getName() | Return whether the reflected constant entry is an enum case |
ReflectionClassConstant::isPublic() | Same as ReflectionClassConstant::getName() | Return whether the reflected class constant or enum case is public |
ReflectionClassConstant::isProtected() | Same as ReflectionClassConstant::getName() | Return whether the reflected class constant is protected; enum cases report false |
ReflectionClassConstant::isPrivate() | Same as ReflectionClassConstant::getName() | Return whether the reflected class constant is private; enum cases report false |
ReflectionClassConstant::isFinal() | Same as ReflectionClassConstant::getName() | Return whether the reflected class/interface/trait/enum constant is final; enum cases report false |
ReflectionClassConstant::isDeprecated() | Same as ReflectionClassConstant::getName() | Return PHP’s current non-deprecated default for retained class-constant metadata |
ReflectionClassConstant::hasType() | Same as ReflectionClassConstant::getName() | Return whether the reflected class constant has a declared PHP 8.3 type |
ReflectionClassConstant::getType() | Same as ReflectionClassConstant::getName() | Return a retained ReflectionNamedType, ReflectionUnionType, or ReflectionIntersectionType, or null for an untyped constant |
ReflectionClassConstant::getModifiers() | Same as ReflectionClassConstant::getName() | Return PHP’s ReflectionClassConstant::IS_* visibility/finality bitmask |
ReflectionEnumUnitCase::getName() / ReflectionEnumBackedCase::getName() | new ReflectionEnumUnitCase($enum_name, $case_name) or new ReflectionEnumBackedCase($enum_name, $case_name) | Return the reflected enum-case name |
ReflectionEnumUnitCase::getValue() / ReflectionEnumBackedCase::getValue() | Same as enum-case getName() | Return the reflected enum-case object |
ReflectionEnumBackedCase::getBackingValue() | new ReflectionEnumBackedCase($enum_name, $case_name) | Return the scalar backing value for the reflected backed enum case |
ReflectionEnumUnitCase::getAttributes() / ReflectionEnumBackedCase::getAttributes() | Same as enum-case getName() | Return ReflectionAttribute objects for enum-case attributes |
ReflectionEnumUnitCase::getDeclaringClass() / ReflectionEnumBackedCase::getDeclaringClass() | Same as enum-case getName() | Return a ReflectionClass object for the enum that declares the reflected case |
ReflectionEnumUnitCase::getEnum() / ReflectionEnumBackedCase::getEnum() | Same as enum-case getName() | Return a ReflectionEnum object for the enum that declares the reflected case |
ReflectionEnumUnitCase::isDeprecated() / ReflectionEnumBackedCase::isDeprecated() | Same as enum-case getName() | Return PHP’s current non-deprecated default for retained enum-case metadata |
ReflectionEnumUnitCase::hasType() / ReflectionEnumBackedCase::hasType() | Same as enum-case getName() | Return false because enum cases expose no class-constant type metadata |
ReflectionEnumUnitCase::getType() / ReflectionEnumBackedCase::getType() | Same as enum-case getName() | Return null for enum-case type metadata |
ReflectionAttribute::newInstance() | Internal only | Instantiate the attribute class from captured literal positional/named args |
Functions and their parameters can also be reflected. ReflectionFunction reads
a named function’s signature, and getParameters() returns one
ReflectionParameter per declared parameter, in order:
<?php
class Mailer {}
function send(string $to, Mailer $mailer, int $retries = 3, ?string $subject = null): void {}
$fn = new ReflectionFunction('send');
echo $fn->getNumberOfParameters(); // 4
echo $fn->getNumberOfRequiredParameters(); // 2
foreach ($fn->getParameters() as $param) {
echo $param->getName(); // to, mailer, retries, subject
echo $param->getPosition(); // 0, 1, 2, 3
echo $param->isOptional() ? "?" : "!"; // retries and subject are optional
if ($param->hasType()) {
$type = $param->getType(); // ReflectionNamedType
echo $type->getName(); // string, Mailer, int, string
echo $type->isBuiltin() ? "b" : "c";
echo $type->allowsNull() ? "n" : "-"; // subject (?string) allows null
}
}
A parameter with no type hint reports hasType() as false, and getType()
returns null. A nullable hint such as ?string reports getName() as
string with allowsNull() true. Class-typed parameters report the bare class
name with isBuiltin() false.
| Reflection method | Returns | Description |
|---|---|---|
ReflectionFunction::getName() | string | The reflected function’s name |
ReflectionFunction::getShortName() | string | The name without its namespace prefix |
ReflectionFunction::getNumberOfParameters() | int | Total declared parameters |
ReflectionFunction::getNumberOfRequiredParameters() | int | Parameters without a default and before the first optional |
ReflectionFunction::getParameters() | ReflectionParameter[] | One object per declared parameter, in order |
ReflectionParameter::getName() | string | The parameter name (without $) |
ReflectionParameter::getPosition() | int | Zero-based parameter index |
ReflectionParameter::isOptional() | bool | True for a parameter with a default or variadic, and any after it |
ReflectionParameter::isVariadic() | bool | True for the ...$rest parameter |
ReflectionParameter::isPassedByReference() | bool | True for by-reference parameters |
ReflectionParameter::canBePassedByValue() | bool | False for by-reference parameters |
ReflectionParameter::isPromoted() | bool | True for constructor-promoted parameters |
ReflectionParameter::hasType() | bool | True when the parameter declares a type |
ReflectionParameter::getType() | ?ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType | The declared type, or null when untyped |
ReflectionParameter::allowsNull() | bool | PHP nullability for the retained parameter metadata |
ReflectionParameter::isArray() / isCallable() | bool | Legacy named-type predicates for direct array / callable hints |
ReflectionParameter::getAttributes() | ReflectionAttribute[] | The reflected function or method parameter’s attributes |
ReflectionParameter::isDefaultValueAvailable() | bool | True when a supported default value is available |
ReflectionParameter::getDefaultValue() | mixed | The scalar, null, class-constant, array literal, or zero-argument object default value, or throws when unavailable |
ReflectionParameter::isDefaultValueConstant() | bool | True when the retained default came from a named constant |
ReflectionParameter::getDefaultValueConstantName() | ?string | The constant name for constant-backed defaults, or null |
ReflectionNamedType::getName() | string | The type name (int, string, a class name, …) |
ReflectionNamedType::isBuiltin() | bool | True for builtin types, false for class types |
ReflectionNamedType::allowsNull() | bool | True when the declared type is nullable |
ReflectionUnionType::getTypes() | ReflectionNamedType[] | The non-null named members of a supported union |
ReflectionUnionType::allowsNull() | bool | True when the union includes null |
ReflectionIntersectionType::getTypes() | ReflectionNamedType[] | The named members of a supported intersection |
ReflectionIntersectionType::allowsNull() | bool | Always false for supported intersections |
Limitations today:
-
All arguments to
class_attribute_names(),class_attribute_args(),class_get_attributes(), andnew ReflectionClass/Function/Method/Property/Parameter(...)must be compile-time class/member/function strings or literal parameter selectors.ClassName::classis accepted for supported class-name arguments, and normal named-argument / static associative-spread normalization runs before the literal-string check.ReflectionClasscan resolve statically known classes, enums, interfaces, traits, and statically typed object expressions, including anonymous-class objects; class-attribute materialization is currently backed by class/enum metadata. Dynamic class, method, property, function, parameter, or attribute names require a runtime name->id lookup table that is not yet implemented. -
Attribute arguments materialized by reflection today include: string, int, float, bool, null, negation (
-N) of a numeric literal,ClassName::classstrings, arrays (positional and associative, nested, with heterogeneous element types), named arguments, and symbolic references - a global constant (#[A(SOME_CONST)]), a class/interface constant (#[A(C::BAR)]), or an enum case (#[A(E::Case)]).ReflectionClass/ReflectionMethod/ReflectionProperty::getAttributes()->getArguments()returns named arguments under their string keys and positional arguments under their integer keys, matching PHP. A constant reference resolves to its value and an enum-case reference resolves to the case object (so reading its->value/->nameworks just like PHP), andnewInstance()constructs the attribute with those arguments. -
A symbolic reference that elephc cannot resolve - for example a built-in class constant such as
Attribute::TARGET_CLASS, which is not registered - is treated as unsupported metadata: the attribute still parses and compiles andclass_attribute_names()still lists it, but its arguments are not reflectable throughgetAttributes()/class_get_attributes()/class_attribute_args(). -
The flat
class_attribute_args()helper materializes supported literal and array values directly, but symbolic references are resolved throughReflectionClass::getAttributes()->getArguments()/ReflectionAttribute::newInstance()instead. -
When several attributes share a name on the same class,
class_attribute_args()returns the args of the first match;class_get_attributes()does expose every occurrence as a separateReflectionAttributein source order. -
ReflectionClasssupportsgetName(),getShortName(),getNamespaceName(),inNamespace(),getAttributes(),isFinal(),isAbstract(),isInterface(),isTrait(),isEnum(),isReadOnly(),isAnonymous(),isInstantiable(),isCloneable(),isIterable(),isIterateable(),isInternal(),isUserDefined(),getModifiers(),hasMethod(),hasProperty(),hasConstant(),getConstant(),getConstants(),getDefaultProperties(),getReflectionConstant(),getReflectionConstants(),implementsInterface(),isSubclassOf(),isInstance(),getInterfaceNames(),getInterfaces(),getTraitNames(),getTraits(),getTraitAliases(),getMethods(),getConstructor(),getParentClass(),getProperties(),newInstance(),newInstanceArgs(), andnewInstanceWithoutConstructor(). Its constructor accepts class-name strings and object arguments; object arguments reflect the runtime class of eval-created or generated/AOT objects. It also exposes PHP-compatibleReflectionClass::IS_IMPLICIT_ABSTRACT,ReflectionClass::IS_FINAL,ReflectionClass::IS_EXPLICIT_ABSTRACT, andReflectionClass::IS_READONLYconstants.ReflectionClass::implementsInterface()returns the metadata predicate for known interface names and throws PHP-compatibleReflectionExceptionmessages when the argument is missing or names a non-interface class-like symbol.ReflectionClass::isSubclassOf()returns the metadata predicate for known class/interface targets, excludes the reflected symbol itself, returnsfalsefor trait and enum targets, and throws PHP-compatibleReflectionExceptionmessages for missing names.ReflectionClass::isInstance()checks the provided object against the reflected class/interface/enum metadata and returnsfalsefor trait targets.ReflectionClass::getDefaultProperties()returns supported materialized property defaults, including static and instance properties, omits typed properties without an initializer, and reports implicitnullfor untyped concrete properties without an explicit initializer.ReflectionClass::newInstance()constructs eval-declared and bridge-supported generated/AOT reflected classes with public constructors and forwards positional, named, unpacked, and defaulted constructor arguments through eval’s call binding.ReflectionClass::newInstanceArgs()constructs those reflected classes from indexed or string-keyed argument arrays, including arrays built at eval runtime; string keys become named constructor arguments. When the receiver is an inlinenew ReflectionClass(Known::class)expression, named arguments, static string-keyed unpacking, and constructor defaults are normalized through the reflected constructor signature.ReflectionClass::newInstanceWithoutConstructor()allocates concrete non-enum reflected classes with supported property defaults while skipping__construct(); specialized internal classes with runtime-owned storage still require dedicated support. Dynamic unpacking and named-argument forwarding from aReflectionClassobject held only in runtime storage still require richer runtime constructor binding.ReflectionFunctionsupportsgetName(),getShortName(),getNamespaceName(),inNamespace(),isInternal(),isUserDefined(),isClosure(),isDeprecated(),returnsReference(),isGenerator(),getAttributes(),getParameters(),getNumberOfParameters(),getNumberOfRequiredParameters(),hasReturnType(),getReturnType(),isVariadic(),hasTentativeReturnType(),getTentativeReturnType(),getClosureUsedVariables(),invoke(),invokeArgs(), andisDisabled()for statically known user functions and supported first-class-callable builtins. AOT invocation supports reflected user functions with declared or inferred/untyped parameter contracts, plus supported callable builtins. Inside eval fragments, registered generated/AOT free-function defaults are reflected throughReflectionParametermetadata.ReflectionMethodconstruction accepts class-name strings and object arguments; object arguments resolve to the runtime class before method lookup.ReflectionMethodsupportsgetName(),getShortName(),getNamespaceName(),inNamespace(),isInternal(),isUserDefined(),isClosure(),isDeprecated(),returnsReference(),isGenerator(),hasPrototype(),getPrototype(),getDeclaringClass(),getAttributes(),getParameters(),getNumberOfParameters(),getNumberOfRequiredParameters(),hasReturnType(),getReturnType(),isVariadic(),hasTentativeReturnType(),getTentativeReturnType(),isStatic(),isPublic(),isProtected(),isPrivate(),isFinal(),isAbstract(),isConstructor(),isDestructor(), andgetModifiers(). It also exposes PHP-compatibleReflectionMethod::IS_PUBLIC,IS_PROTECTED,IS_PRIVATE,IS_STATIC,IS_FINAL, andIS_ABSTRACTconstants.ReflectionParametersupports direct construction for statically known user function names or supported callable-builtin names ("function"), class/interface/trait method arrays ([ClassLike::class, "method"]), and statically typed object-expression method arrays ([$object_expr, "method"]) outside eval; those object expressions are still evaluated before reflection metadata is materialized. Inside eval fragments it can also resolve object-method arrays from evaluated runtime objects, including inlinenewexpressions. It also supportsgetName(),getPosition(),isOptional(),isVariadic(),isPassedByReference(),canBePassedByValue(),isPromoted(),hasType(),getType()for simple named, union, and intersection parameter types,allowsNull(),isArray(),isCallable(),getAttributes()for function and method parameter attributes,getDeclaringClass(),getDeclaringFunction(),isDefaultValueAvailable(),getDefaultValue(),isDefaultValueConstant(), andgetDefaultValueConstantName()for supported scalar/null/class-constant/default-array metadata and object defaults with up to eight supported scalar/null constructor arguments from literals or constants, including omitted constructor defaults.ReflectionNamedTypesupportsgetName(),allowsNull(), andisBuiltin().ReflectionUnionTypeandReflectionIntersectionTypesupportgetTypes()andallowsNull().ReflectionPropertysupportsgetName(),getDeclaringClass(),getAttributes(),isStatic(),isPublic(),isProtected(),isPrivate(),isFinal(),isAbstract(),isReadOnly(),isPromoted(),isProtectedSet(),isPrivateSet(),getModifiers(),isDefault(),isDynamic(),isLazy(),skipLazyInitialization(),__toString(),hasHooks(),getHooks(),hasHook(),getHook(),hasType(),getType(),getSettableType(),hasDefaultValue(),getDefaultValue()for supported property defaults, andisInitialized()for supported known property slots. It also exposes PHP-compatibleReflectionProperty::IS_PUBLIC,IS_PROTECTED,IS_PRIVATE,IS_STATIC,IS_FINAL,IS_ABSTRACT,IS_READONLY,IS_VIRTUAL,IS_PROTECTED_SET, andIS_PRIVATE_SETconstants.ReflectionClassConstantsupportsgetName(),getValue(),getAttributes(),getDeclaringClass(),isEnumCase(),isPublic(),isProtected(),isPrivate(),isFinal(),isDeprecated(),hasType(),getType()for retained named, nullable, union, and intersection declarations, andgetModifiers(). It also exposes PHP-compatibleReflectionClassConstant::IS_PUBLIC,IS_PROTECTED,IS_PRIVATE, andIS_FINALconstants.ReflectionEnumUnitCaseandReflectionEnumBackedCasesupportgetName(),getValue(),getAttributes(), andgetDeclaringClass(), andReflectionEnumBackedCasealso supportsgetBackingValue(). The enum-case reflectors expose the untypedhasType()/getType()defaults. Broader APIs such as object parameter defaults beyond that limited literal constructor-argument slice, broader internal-function metadata beyond supported first-class-callable builtin signatures, and runtime-only/dynamically typed object-expressionnew ReflectionParameter(...)construction outside eval fragments are not yet available.ReflectionClassConstant::getValue()returns the reflected class-constant value, or the enum-case object when the reflected constant is an enum case. An explicitmixedclass-constant hint is retained as a declared type. -
ReflectionObjectsupports the inherited class-metadata methods for object arguments and materializes the same metadata through aReflectionObjectinstance. -
ReflectionEnumadditionally supports backing metadata (isBacked(),getBackingType()); inside eval fragments it also supports enum-case lookup (hasCase(),getCase(),getCases()). Enum-case reflectors exposegetEnum(). -
ReflectionNamedType,ReflectionUnionType, andReflectionIntersectionTypealso support__toString()for retained named, nullable, union, and intersection metadata. -
For object parameter defaults, that limited constructor-argument slice accepts supported scalar/null literals and class constants; omitted constructor defaults are expanded when they stay within the same subset.
-
ReflectionProperty::hasHooks(),getHooks(),hasHook(PropertyHookType $type), andgetHook(PropertyHookType $type)expose retained concrete generated/AOT property get/set hooks as string-keyed or singleReflectionMethodobjects.PropertyHookType::Get,PropertyHookType::Set,cases(),from(), andtryFrom()are available for those hook APIs.ReflectionProperty::getValue()andsetValue()are supported for instance-property reflectors with explicit positional or named object/value arguments, including private/protected properties, reflector objects held in variables, and entries returned fromReflectionClass::getProperties().ReflectionProperty::isInitialized()is supported for inline or straight-line tracked instance/static property reflectors and checks the typed-property initialization sentinel without reading the value, including private/protected slots and initializednullvalues. Static-property value access is supported for inlinenew ReflectionProperty(Known::class, "property")receivers, inlineReflectionClass::getProperty("property")receivers,ReflectionClass::getProperties()[N]andReflectionClass::getProperties(<static modifier filter>)[N]receivers with a known numeric index, and straight-line locals assigned from those forms with omitted/ignored object arguments, including private/protected static properties. Runtime-held staticReflectionPropertyobjects can readgetValue()andisInitialized()from the materialized declaring-class static-property snapshot. Live refresh after reflector construction and dynamic staticsetValue()still require richer runtime metadata. -
ReflectionFunction::invoke()andinvokeArgs()are supported for inline or straight-line tracked generated/AOT reflectors with declared or inferred/untyped parameter contracts, and for supported callable-builtin reflectors. The lowered path supports function-name case-insensitivity, defaults, named arguments, inline staticinvokeArgs()argument arrays, and straight-line locals assigned from literal argument arrays; untyped parameters are forwarded through the boxedMixedABI used by EIR. Inside eval fragments, runtime-held generated/AOT function reflectors can also invoke registered generated functions through the native bridge with parameter names, supported defaults, named arguments, indexed or string-keyed runtime argument arrays, and reflected default metadata for supported parameter defaults. Runtime-only generated/AOT function invocation outside eval still requires richer runtime/typechecker support. -
ReflectionMethod::invoke()andinvokeArgs()are supported for inline or straight-line tracked generated/AOT reflectors with declared or inferred/untyped parameter contracts. The lowered path supports instance and static methods, constructors returned byReflectionClass::getConstructor(), method-name case-insensitivity, defaults, named arguments, inline staticinvokeArgs()argument arrays, and straight-line locals assigned from literal argument arrays; untyped parameters are forwarded through the boxedMixedABI used by EIR. Runtime-only or non-literal argument arrays still require richer runtime/typechecker support. -
ReflectionClass::getMethods()andgetProperties()modifier filters are supported for materialized member lists, including runtime-heldReflectionClassobjects. Inline or straight-line trackedReflectionClass(Known::class)receivers with known integer orReflectionMethod::IS_*/ReflectionProperty::IS_*filters can also be statically narrowed before materialization. -
ReflectionClass::getStaticProperties(),getStaticPropertyValue(), andsetStaticPropertyValue()expose current static-property values for eval-declared classes. For generated/AOT classes, these methods are lowered to live static-property storage, including private/protected static properties, when the receiver is an inlinenew ReflectionClass(Known::class)expression, or a straight-line local assigned from one; single-property get/set calls additionally require a literal property name. Runtime-held generated/AOTReflectionClassobjects also carry a materialized static-property map captured when the reflector is constructed; it includes initialized public/protected/private static properties, preserves initializednullvalues, omits uninitialized typed static properties, and letsgetStaticProperties()plus map-backedgetStaticPropertyValue()keep working after the reflector is passed through runtime storage. Missing literal properties can return the explicit default argument on both the tracked live path and the materialized-map path; the tracked live path also throws a catchableReflectionExceptionwhen no default is provided. The same tracked receiver metadata letsnewInstance()andnewInstanceArgs()normalize named constructor arguments through the reflected constructor signature, including straight-line locals assigned from literal argument arrays. Fully dynamic class/property lookup, live refresh after reflector construction, and dynamicsetStaticPropertyValue()still require richer runtime metadata. -
ReflectionObjectconstruction helpers normalize named constructor arguments through the reflected constructor signature for straight-line receivers built from statically typed objects.
ReflectionParameter::canBePassedByValue(), allowsNull(), isArray(),
isCallable(), and getClass() are supported for retained parameter metadata.
isArray() / isCallable() follow PHP’s legacy behavior: nullable or
non-nullable direct array / callable declarations report true, while
union declarations report false. getClass() follows PHP’s legacy behavior
for nullable or non-nullable named object types and reports null for builtin,
union, intersection, or untyped parameters.
ReflectionProperty::isDefault() is also supported for materialized declared
and dynamic properties. Declared properties report true; supported dynamic
properties materialized with new ReflectionProperty($object, $property_name)
report false.
ReflectionProperty::isDynamic() reports false for supported declared
properties and true for public dynamic object properties materialized with
new ReflectionProperty($object, $property_name).
ReflectionProperty::isLazy() reports false, and
skipLazyInitialization() is accepted as a no-op for supported non-lazy
declared properties.
Class constants
<?php
class Math {
const int PI = 314;
public const int|float E = 271;
const TAU = self::PI * 2;
}
echo Math::PI; // 314
echo self::PI; // inside Math methods
interface Limits {
const MAX = 100;
}
class Bound implements Limits {
public function get(): int { return Limits::MAX; }
}
Class constants (PHP 7.1+ visibility, PHP 8.1+ final, PHP 8.3+ declared types) live on classes, interfaces, traits, and enums. Names are case-sensitive and may use PHP keywords other than the reserved class name; exact declaration and access spelling is preserved. Declared types are enforced on initializer values, and an overriding constant must preserve or narrow an inherited class/interface type. PHP-forbidden constant types (void, never, and callable) are rejected. Typed constants expose their declared named, nullable, union, or intersection metadata through ReflectionClassConstant::hasType() and getType(); untyped constants and enum cases continue to report false and null.
Constants are inherited from parents and implemented interfaces (transitively). At codegen time elephc inlines the constant’s foldable value at every access site — there is no runtime lookup. Class constant expressions may reference other class constants through ClassName::CONST, self::CONST, or parent::CONST; self::class and parent::class are also accepted. self:: and parent:: are early-bound to the declaring class, matching PHP. static::CONST is rejected in class constant expressions because PHP does not allow late-static binding in compile-time constants. Attributes on class constants are accepted and retained for reflection.
Limitations
readonly staticproperties are rejected to match PHP. Static properties in areadonly classare still mutable.- Backed property hooks may read and write their own backing slot.
unset()is supported on typed declared properties (the slot becomes uninitialized) and on dynamic properties (stdClass, undeclared names on#[AllowDynamicProperties]classes, where the entry is removed). It is rejected on untyped declared properties, on by-reference properties, and on dynamic names of a class that also declares__unset(). See “unset()limitations” above.- Class constants must be literal-or-foldable expressions; cyclic constant references are not supported.
- Class and function attribute names and supported literal args are exposed at runtime through
class_attribute_names(),class_attribute_args(),class_get_attributes(), and the supportedReflectionClass/ReflectionFunction/ReflectionMethod/ReflectionProperty/ReflectionClassConstant/ReflectionEnumUnitCase/ReflectionEnumBackedCase::getAttributes()APIs; function and method parameter names, counts, positions, optional/variadic/by-reference flags, declared-type presence, simple named, union, and intersection type metadata, function and method parameter attributes, supported scalar/null/class-constant/array/object parameter defaults, parameter declaring-class/function metadata, and reflected member/constant declaring-class metadata are exposed through the supported Reflection APIs.#[\Override],#[\Deprecated], and#[\AllowDynamicProperties]are enforced/diagnosed/honored at compile time and runtime;#[\SensitiveParameter]is parsed but not yet propagated to stack traces.