Every reserved keyword, language construct, type cast, and library function the current phplang build recognizes, grouped by keyword then construct then cast then library area. This page is generated from the language corpus (src/lsp.rs) by the gen-docs binary, so it stays in sync with what the runtime actually implements. Keywords/constructs mirror lexer.rs and parser.rs; each library function mirrors a real dispatch arm in src/builtins.rs.
Keyword
# if
if (expr) statement
Conditional branch; runs its body when the condition is truthy. The body is a single statement or a braced block. PHP's alternative `if: … endif;` syntax is not parsed.
if (1 < 2) echo "y"; // => y
# elseif
if (expr) stmt elseif (expr) stmt
Additional condition tested when every preceding one failed. The two-word spelling `else if` is folded into the same branch list by the parser, so both forms behave identically.
if (0) echo "a"; elseif (1) echo "b"; // => b
# else
if (expr) stmt else stmt
Fallback branch taken when the `if` condition and every `elseif` condition are falsey.
if (0) echo "a"; else echo "b"; // => b
# while
while (expr) statement
Loops while the condition stays truthy, testing before each pass. The alternative `while: … endwhile;` syntax is not parsed.
$i = 0; while ($i < 3) $i++; echo $i; // => 3
# do
do statement while (expr);
Post-tested loop; the body always runs at least once. The trailing `while (…)` and its semicolon are both required.
$i = 5; do $i++; while ($i < 3); echo $i; // => 6
# for
for (init; cond; step) statement
C-style loop. Each of the three clauses is a comma-separated expression list and each may be empty; an empty condition loops forever.
$s = 0; for ($i = 1; $i <= 3; $i++) $s += $i; echo $s; // => 6
# foreach
foreach (expr as [&]$value) statement
foreach (expr as $key => [&]$value) statement
Iterates an array. A `&` before the value variable iterates by reference and writes each assignment back into the array. Only arrays and generators are iterable — no user class implements `Iterator`.
foreach ([1, 2] as $v) echo $v; // => 12
# as
foreach (expr as $v)
use A { m as [visibility] [alias]; } // trait adaptation
use Ns\Name as Alias; // namespace import
Binds the current element inside a `foreach` header. In a trait adaptation block it binds a trait method under a second name, a new visibility, or both — `m as x;` adds `x` alongside `m`, `m as protected;` only re-marks `m`. In a namespace import the alias is accepted and discarded, because phplang resolves every name in one flat namespace.
foreach ([9] as $k => $v) echo $k; // => 0
# insteadof
use A, B { A::m insteadof B[, C]; } // inside a class body
Resolves a trait method collision by EXCLUDING the named traits' version of the method, inside the `{ … }` block of a `use`. Two used traits declaring the same method with no `insteadof` between them is a fatal error, and excluding only one of three still leaves the other two colliding. The excluded method is not gone — an `as` alias in the same block can still bind it under another name.
trait A { function m() { return "A"; } }
trait B { function m() { return "B"; } }
class C { use A, B { A::m insteadof B; B::m as bm; } }
$c = new C; echo $c->m(), $c->bm(); // => AB
# switch
switch (expr) { case expr: … default: … }
Multi-way branch; each `case` compares against the subject with loose (`==`) equality. A case label may end in `:` or `;`. The alternative `switch: … endswitch;` syntax is not parsed.
switch (2) { case 2: echo "two"; } // => two
# case
case expr: // also: case Name [= value]; inside an enum
A labelled `switch` branch, falling through into the next branch until a `break`. Inside an `enum` body the same keyword declares a case, optionally with a backing value.
switch (1) { case 1: echo "a"; case 2: echo "b"; } // => ab
# default
default: // in switch; default => expr // in match
The branch taken when no case matched — in a `switch` body and in a `match` expression.
switch (9) { default: echo "d"; } // => d
# break
break;
break <int>; // level parsed, then IGNORED
Exits the nearest enclosing loop or `switch`. DIVERGENCE: the optional numeric level is parsed and discarded (`src/parser.rs`), so `break 2;` unwinds ONE level exactly like `break;` instead of two. Nested loops therefore keep running where reference PHP would have left them.
for ($i=0;$i<2;$i++){ for ($j=0;$j<2;$j++){ echo $i.$j." "; break 2; } }
echo "end"; // => 00 10 end (PHP 8 prints: 00 end)
# continue
continue;
continue <int>; // level parsed, then IGNORED
Skips to the next iteration of the nearest loop. DIVERGENCE: like `break`, the numeric level is parsed and discarded, so `continue 2;` only restarts the innermost loop.
foreach ([1, 2, 3] as $v) { if ($v == 2) continue; echo $v; } // => 13
# declare
declare(directive=value);
declare(directive=value) { body } // not for strict_types
Sets a compilation-unit directive. `strict_types=1` turns off the coercion a scalar parameter or return type would otherwise perform, so a value must already be of the declared type — with int→float widening the one exception, since it cannot lose anything. Its rules are all COMPILE-time, and each has its own message: it must be the very first statement (a preceding `declare` does not count against it, any other statement does), its value must be the literal `0` or `1`, and it may not use the block form even though `ticks` may. `ticks` is accepted and ignored — no tick handler exists to call. `encoding` is recognised and refused with the reference's own wording; any other directive warns `Unsupported declare`. DIVERGENCE: upstream reads the mode from the CALLER's file, so a strict file calling a non-strict file's function still checks strictly; phplang has no `include`, so a run is one file and the two readings coincide.
declare(strict_types=1); function f(int $x) { return $x; } f("5"); // => TypeError
# function
function name(params) [: type] { body }
function (params) [use (vars)] [: type] { body } // closure
Declares a named function as a statement, or an anonymous closure as an expression. A scalar parameter or return type (`int`, `float`, `string`, `bool`, and their `?` nullable forms) is ENFORCED: coerced on the way in by default, required exactly under `declare(strict_types=1)`. Any other type — a union, an intersection, a class name, `array`, `iterable`, `callable`, `mixed`, `object`, `void`, `never`, `static` — is parsed and carried but imposes no check. A function with no `return` yields null.
function sq($x) { return $x * $x; } echo sq(4); // => 16
# fn
fn(params) [: type] => expr
Arrow function: a single-expression body that automatically captures every free variable by value. Returns a real `Closure`.
$d = fn($x) => $x * 2; echo $d(4); // => 8
# use
function (…) use ($a, $b) { … }
use Ns\Name [as Alias];
use T1, T2 [{ adaptations }]; // inside a class body
Three unrelated roles. In a closure header it captures the named enclosing variables by value — DIVERGENCE: by-reference capture `use (&$v)` is rejected with a parse error rather than silently binding by value. As a statement it is a namespace import, accepted and discarded. Inside a class body it pulls the traits' members in, with an optional `{ … }` block of `insteadof`/`as` adaptations resolving collisions and binding extra names.
$n = 10; $f = function () use ($n) { return $n; }; echo $f(); // => 10
# return
return [expr];
Returns a value from the current function, or null when the expression is omitted. At the top level it ends the script.
function one() { return 1; } echo one(); // => 1
# yield
yield;
yield $value;
yield $key => $value;
Suspends the enclosing generator and hands a value (and optionally a key) to the consumer; the expression evaluates to whatever the next `->send()` passes in. Any function containing `yield` becomes a generator, run on its own native stack.
function g() { yield 1; yield 2; } foreach (g() as $v) echo $v; // => 12
# yield from
yield from expr
Delegates to an inner generator or array, re-yielding each of its elements before continuing.
function inner() { yield 1; yield 2; }
function outer() { yield from inner(); yield 3; }
foreach (outer() as $v) echo $v; // => 123
# match
match (expr) { cond[, cond] => expr, … , default => expr }
PHP 8 expression that compares the subject to each arm with strict (`===`) equality and evaluates the first hit. With no matching arm and no `default` it throws `UnhandledMatchError` rather than yielding null.
echo match (2) { 1 => "a", 2 => "b" }; // => b
try { echo match (5) { 1 => "a" }; }
catch (UnhandledMatchError $e) { echo $e->getMessage(); } // => Unhandled match case 5
# array
array(elem, …) // identical to [elem, …]
The long-form array literal constructor. It parses to exactly the same node as the `[…]` short form, so keys, `=>` pairs, and destructuring behave identically.
echo count(array(1, 2, 3)); // => 3
# list
list($a, $b) = expr;
list('k' => $v) = expr;
The long-form destructuring target. It desugars to the same array node as `[$a, $b] = …`, so both forms share one assignment path. An empty slot (`list(, $b)`) still consumes a positional index.
list($a, $b) = [1, 2]; echo $a . $b; // => 12
# clone
clone $object
Duplicates an object: a new instance of the same class carrying a copy of the properties, then `__clone()` on the copy if the class defines one. The copy is shallow the way PHP means it — an array property is a value and travels as a copy, an object property is a handle and stays shared with the original. Binds tighter than every operator, so `clone $a->b` clones the property and `clone $a instanceof C` tests the clone. A non-object is `TypeError: clone(): Argument #1 ($object) must be of type object, int given`; a live generator holds a suspended stack and is refused with `Error: Trying to clone an uncloneable object of class Generator`.
class C { public $n = 1; } $a = new C; $b = clone $a; $b->n = 2; echo $a->n, $b->n; // => 12
# class
[abstract|final|readonly] class Name [extends Parent] [implements I, …] { members }
Declares a class: constants, properties, static properties, and methods. `abstract` marks the class un-instantiable; `readonly` makes every property it declares readonly; `final` is parsed and ignored.
class C { public $x = 1; } $o = new C; echo $o->x; // => 1
# interface
interface Name [extends I, …] { const …; function m(); }
Declares an interface, stored in the same class table as classes. `interface_exists()` reports it and `class_exists()` does NOT, as in PHP, where each existence predicate answers for one declaration kind.
interface I {} class C implements I {} $o = new C;
var_dump($o instanceof I, interface_exists("I"), class_exists("I")); // => bool(true) bool(true) bool(false)
# trait
trait Name { members } // then: use Name; inside a class
Declares a reusable member bundle that a class pulls in with `use`, optionally adapted by an `insteadof`/`as` block. A `use` naming a trait that was never declared is a throwable `Error`; an unresolved method collision is a fatal raised when the declaration is reached, so output produced before it is still printed. DIVERGENCE: two definitions of the same property with different defaults are merged silently rather than refused.
trait T { function f() { return 3; } } class C { use T; } echo (new C)->f(); // => 3
# enum
enum Name [: backing-type] [implements I] { case A [= value]; … }
Declares an enum. Cases are class constants holding a case object with `->name` and, for a backed enum, `->value`. Every enum implicitly implements `UnitEnum`, and a backed one also `BackedEnum`. `enum` is only treated as a declaration when a name and `{`, `:`, or `implements` follow, so a bareword `enum` still parses as an identifier. An enum answers yes to both `enum_exists()` and `class_exists()`, as in PHP.
enum Suit: string { case Hearts = "H"; } echo Suit::Hearts->value; // => H
# extends
class C extends Parent // interface I extends A, B
Single inheritance for a class — the child inherits its parent's methods, properties, and constants. An interface may extend several parents, which the parser records as implemented interfaces.
class A { function f() { return 1; } } class B extends A {} echo (new B)->f(); // => 1
# implements
class C implements I, J
Lists the interfaces a class satisfies. `instanceof` and `class_implements()` both walk this list transitively, including through parent classes.
interface I {} class C implements I {} var_dump((new C) instanceof I); // => bool(true)
# new
new ClassName[(args)]
new class[(args)] [extends P] [implements I, …] { members }
Instantiates a class, running `__construct` with the given arguments. The parentheses are optional when there are no arguments. The class name is a bareword — `new $className` is not parsed. `new class` declares the class inline: its arguments come BEFORE the `extends`/`implements` clauses, and the declaration is compiled once, so the same site in a loop yields instances of one class. Its generated name is `Base@anonymous\0<script>:<line>$<n>` — the parent, else the first interface, else `class` — so `get_class` returns a unique string while `var_dump` and diagnostics print the readable head alone.
class C { function __construct($x) { $this->x = $x; } } echo (new C(5))->x; // => 5
# const
const NAME = expr[, NAME2 = expr2]*;
Declares a constant. Inside a class or interface body it is a class constant, read back through `ClassName::NAME`. At statement level it is a GLOBAL constant, read back as a bareword — the declaration spelling of `define()`, and top-level only, so a function body or an `if` block rejects it the way PHP's grammar does. It is not hoisted: the constant exists from the statement onwards, so a `defined()` above it answers false. Redefining warns and keeps the FIRST value, through either spelling. Several names may be declared in one comma-separated statement, and a later one may read an earlier one.
class A { const K = 7; } echo A::K; // => 7
const G = 1, H = G + 1; echo H; // => 2
# static
static $var [= expr];
static function m() { … }
static public $prop;
static::member
Four roles, told apart by what follows. Before a `$variable` in a function body it declares a static local that survives between calls; before a member it marks it static; as a `::` qualifier it is late static binding; and before `function`/`fn` it makes a CLOSURE static, so it is not bound to the `$this` of the method it was written in and `Closure::bind` refuses to give it one afterwards (warning `Cannot bind an instance to a static closure, this will be an error in PHP 9` and answering null). It keeps that method's class scope, so a private static stays reachable.
class A { static $n = 4; } echo A::$n; // => 4
$f = static fn($x) => $x + 1; echo $f(1); // => 2
# public
public $prop; public function m() { … } public function __construct(public $x)
Marks a member publicly visible (the default when no visibility keyword is given). In a constructor parameter list it promotes the parameter to a property.
class C { public $x = 2; } echo (new C)->x; // => 2
# protected
protected $prop; protected function m() { … }
Marks a member reachable only from the declaring class and its subclasses. Visibility IS enforced: reading, writing or unsetting the property from outside throws a catchable `Error: Cannot access protected property C::$x` — unless the class defines the matching magic method (`__get`, `__set`, `__unset`), which is consulted first and makes the access succeed instead.
class A { protected $x = 1; }
class B extends A { function g() { return $this->x; } }
echo (new B)->g(); // => 1
# private
private $prop; private function m() { … }
Marks a member reachable only from the declaring class. Enforced for both properties and methods. A property access from outside throws a catchable `Error: Cannot access private property C::$x`, naming the class that DECLARED it, unless the class defines the matching magic method (`__get`, `__set`, `__unset`), which is consulted first. `isset()` never throws here — an unreachable property is simply not set. DIVERGENCE: a method call from outside still aborts with the uncatchable host error `php: Call to private method C::m() from global scope`.
class C { private $x = 4; function get() { return $this->x; } }
echo (new C)->get(); // => 4
# readonly
readonly $prop; function __construct(public readonly $x) readonly class C
Marks a property writable exactly once. Accepted on a property declaration, on a promoted constructor parameter, and on the class itself (`readonly class`, which applies it to every property the class declares, promoted parameters included); a trait's readonly property stays readonly in the class that uses it. The one write must come from the declaring class or a subclass, so an outside write to a still-uninitialized property is `Error: Cannot modify protected(set) readonly property C::$x from global scope` and every later write anywhere is `Error: Cannot modify readonly property C::$x`. Writing THROUGH the property (`$o->arr[] = 1`) is `Error: Cannot indirectly modify readonly property C::$arr` and `unset()` on an initialized one is `Error: Cannot unset readonly property C::$x`. `__clone` is the single reopening: it may rewrite the copy's readonly properties, which lock again when it returns.
class C { public readonly $x; function __construct() { $this->x = 1; } }
$c = new C; try { $c->x = 2; } catch (Error $e) { echo $e->getMessage(); }
// => Cannot modify readonly property C::$x
# abstract
abstract class C { abstract function m(); }
On a class it marks it un-instantiable. On a method it is parsed and discarded; an abstract method is simply a method whose body is `;`, so calling an un-overridden one returns null instead of raising a fatal error.
abstract class A { abstract function f(); } class B extends A { function f() { return 9; } }
echo (new B)->f(); // => 9
# final
final class C { … } final function m() { … }
Parsed and discarded on both classes and methods. DIVERGENCE: a `final` class can still be extended and a `final` method still overridden.
final class A {} class B extends A {} echo get_class(new B); // => B (PHP 8: fatal error)
# var
var $prop;
The PHP 4 property declaration keyword. Accepted and treated as `public`.
class C { var $x = 5; } echo (new C)->x; // => 5
# instanceof
expr instanceof ClassName
True when the value is an object whose class is, extends, or implements the named class — the check walks the parent chain and every implemented interface transitively. `Throwable` is special-cased to match both the `Exception` and `Error` roots.
class A {} class B extends A {} var_dump(new B instanceof A); // => bool(true)
# try
try { body } catch (T [$e]) { … } [finally { … }]
Guards a block. At least one `catch` or a `finally` must follow, matching PHP's own rule.
try { echo "a"; } finally { echo "b"; } // => ab
# catch
catch (A | B [$e]) { body }
Handles a thrown object whose class matches any type in the `|`-separated union. The bound variable is optional (PHP 8 syntax). A leading `\` and any `Ns\` prefix are folded away, so `\RuntimeException` and `RuntimeException` name the same class.
try { throw new Exception("x"); } catch (Exception $e) { echo $e->getMessage(); } // => x
# finally
finally { body }
Runs after the `try`/`catch` on every exit path — normal fall-through, `return`, a rethrown exception, `break`, and `continue`.
try { echo "t"; } finally { echo "f"; } // => tf
# throw
throw expr; // also usable as an expression
Raises an exception. It is both a statement and a PHP 8 expression, so `$x ?? throw new E` and `cond ? throw new E : $v` both parse.
try { throw new Exception("boom"); } catch (Exception $e) { echo $e->getMessage(); } // => boom
# namespace
namespace Name; // or namespace Name { … }
Accepted and discarded: phplang resolves every name in one flat namespace, folding a qualified name to its last segment. The block form runs its body inline.
namespace App; function f() { return 1; } echo f(); // => 1
# true
true
The boolean true literal, matched case-insensitively.
echo true ? "y" : "n"; // => y
# false
false
The boolean false literal, matched case-insensitively.
echo false ? "y" : "n"; // => n
# null
null
The null literal — the absent or unset value, matched case-insensitively. Reading an undefined variable or a missing array key also produces it, quietly.
echo null ?? "fallback"; // => fallback
# and
expr and expr
Logical AND that short-circuits. DIVERGENCE: the parser gives it the same binding power as `&&`, whereas PHP puts `and` below assignment — so `$x = true and false;` assigns `false` here and `true` in PHP 8.
echo (true and false) ? "y" : "n"; // => n
# or
expr or expr
Logical OR that short-circuits. It carries the same precedence as `||`, with the same divergence from PHP's low-precedence `or` described under `and`. There is no `xor` — it is not a token the parser recognizes.
echo (false or true) ? "y" : "n"; // => y
# global
global $var[, $var …];
Binds each name to the GLOBAL variable of that name for the rest of the function. It is a reference, not a copy: a write through either side is seen by the other, and a global that does not exist yet is created by the binding. `unset()` on the local breaks the alias and leaves the global alone. Without the declaration the name is an ordinary, unrelated local. At global scope there is no second frame to bind to and the statement does nothing.
$g = 1; function f() { global $g; $g = 2; } f(); echo $g; // => 2
Language construct
# echo
echo expr[, expr …]; // also: <?= expr ?>
Writes one or more values to standard output and has no return value. The short echo tag `<?=` is the single-argument form. An object argument is converted through `__toString`, as it is everywhere a value becomes a string.
echo "hi", "!"; // => hi!
# print
print expr;
Writes a single value to standard output. DIVERGENCE: it is a statement only. PHP's `print` is an expression that evaluates to `1`, but `$x = print "a";` is a syntax error here.
print "hi"; // => hi
# exit
exit[(string|int $status = 0)]: never // also spelled `die`
Ends the request where it stands. Both the parentheses and the argument are optional, which is what keeps `exit` a construct even though PHP 8.4 also registered it as a callable function (`function_exists("exit")` is true, and `$f = "exit"; $f(3);` works). An INT becomes the process exit status, taken modulo 256 — `exit(300)` leaves 44 and `exit(-1)` leaves 255. A STRING is written to output and the status is 0. A bool or float narrows to int, a float with a fraction raising `Implicit conversion from float … to int loses precision`; an explicit null is deprecated and reads as 0; anything else is a `TypeError`. The unwind is not catchable and does not run a `finally` — `try { exit(5); } finally { echo "fin"; }` prints nothing — but open output buffers are still flushed on the way out.
echo "a"; exit(3); // => a, exit status 3
# die
die[(string|int $status = 0)]: never
The alternative spelling of `exit`, folded onto it by the scanner: the two are one construct, so a `die()` argument PHP rejects is reported against `exit()` and a syntax error on `die` names the `exit` token. See `exit` for the semantics.
die("bye"); // => bye, exit status 0
# isset
isset($var[, $var …]): bool
True when every argument is set and not null, staying quiet about undefined variables and missing keys — asking is never an error, so even an unreachable private property is simply `false` rather than a fatal. On an OBJECT PROPERTY it consults `__isset` and stops there: it never reads a value, so `isset($o->p)` is true whenever `__isset` says so even if `__get` would answer null.
$a = 1; echo isset($a) ? "y" : "n"; // => y
# empty
empty($var): bool
True when the argument is falsey or unset, raising no notice for an undefined name, so `empty("0")`, `empty([])`, and `empty(null)` are all true. On an OBJECT PROPERTY it sits between `isset` and `??`: it wants a value, but will not read one through `__get` unless `__isset` vouched for the property first. A class with `__get` and no `__isset` is therefore `empty()` without `__get` ever being called, while `$o->p ?? "d"` on that same class does call it.
echo empty(0) ? "y" : "n"; // => y
# unset
unset($var[, $var …]): void
Removes variables, array elements and object properties. Like `isset`, it is a construct rather than a call, so an index path such as `$a[$k]` is unset in place rather than being evaluated to a value first. `unset($o->p)` removes the property outright — a later read of it goes through `__get` as for any property that is not there — or calls `__unset` when the property is unreachable or absent and the class defines one. Unsetting a property that is not there is not an error; unsetting one that is out of reach, with no `__unset` to take it, throws `Error: Cannot access private property C::$p`.
$a = [1, 2]; unset($a[0]); echo count($a); // => 1
# #[Attr]
#[Name], #[Name(args)], #[Ns\Name] … // before a class, function, method, property, class constant, enum case, or parameter
An attribute group: declarative metadata attached to the declaration that follows. It is NOT a comment — `#[` and `#` are different tokens, and lexing `#[Attr] class C {}` as a comment would delete the declaration. Arguments are scanned for bracket balance and discarded. DIVERGENCE: attributes are not reflectable (`ReflectionAttribute` does not exist) and none is evaluated; only `#[AllowDynamicProperties]` changes behaviour.
#[Attr(1)] class C { #[Attr] public $v = 7; } echo (new C)->v; // => 7
# #[AllowDynamicProperties]
#[AllowDynamicProperties] class C { … }
Opts a class out of the PHP 8.2 `Deprecated: Creation of dynamic property C::$p is deprecated` notice, which otherwise fires the first time a property no class in the chain declares is written. The opt-out is INHERITED by subclasses. `stdClass` is exempt without it, as is any property the class (or an ancestor) declares — including one promoted from a constructor parameter. The name is matched exactly, so a namespaced `#[Ns\AllowDynamicProperties]` is a different, inert attribute.
#[AllowDynamicProperties] class C {} $c = new C; $c->x = 1; echo $c->x; // => 1
# rust { … }
rust { pub extern "C" fn name(args) -> ty { … } }
Inline Rust FFI. The block is lifted out before lexing and replaced with a `__rust_compile(…)` call carrying the base64-encoded body; every `pub extern "C"` function it exports becomes callable from PHP as a bareword. Unique to phplang — reference PHP has no such construct.
rust { pub extern "C" fn add(a: i64, b: i64) -> i64 { a + b } }
echo add(2, 3); // => 5
# __LINE__
__LINE__: int
The line the constant is WRITTEN on, resolved by the parse rather than looked up at run time. A function reporting `__LINE__` therefore reports where the constant stands inside it, not the line it was called from.
echo __LINE__; // => 1
# __FILE__
__FILE__: string
The running script's name, which is whatever the ENTRY POINT called it: a named file is its resolved path, `php -r` code is `Command line code`, and a script on standard input is `Standard input code`. It is the same name every diagnostic quotes, so the two can never disagree.
echo __FILE__; // => Command line code
# __DIR__
__DIR__: string
The directory part of `__FILE__`. Code with no file behind it — `php -r`, or a script on standard input — has no directory to report, and answers the working directory instead, as the reference does.
var_dump(__DIR__ === getcwd()); // => bool(true)
# __FUNCTION__
__FUNCTION__: string
The enclosing function's name, or `""` at file scope. A CLOSURE has no name of its own, so PHP 8.4 builds it one out of the scope the closure was written in: `{closure:<scope>:<line>}`, where the scope is `__FILE__` at file scope, `f()` inside a function, `C::m()` inside a method — and the closure's own name when closures nest, so the two compose.
function g() { echo __FUNCTION__; } g(); // => g
# __CLASS__
__CLASS__: string
The class the code was WRITTEN in, or `""` outside one — so an inherited method reports the class that declared it, not the subclass the call arrived through. Two cases are answered from the running frame instead, because a parse cannot settle them: inside a TRAIT method it is the class that used the trait (one trait, many users), and inside an anonymous class it is the generated `class@anonymous` name `get_class` reports. A named function declared inside a method body belongs to no class and reports `""`.
class C { function m() { echo __CLASS__; } } class D extends C {} (new D)->m(); // => C
# __METHOD__
__METHOD__: string
`Class::method` for a method, and just the name for a free function — there is no `::` half to prepend outside a class. A trait's method reports the TRAIT (`T::tm`) even though `__CLASS__` in the same body reports the using class. Inside a closure it is the closure's name, the same string `__FUNCTION__` gives.
class C { function m() { echo __METHOD__; } } (new C)->m(); // => C::m
# __NAMESPACE__
__NAMESPACE__: string
The enclosing `namespace` declaration in full — `A\B`, not the last segment a class reference folds to — and `""` with no declaration. DIVERGENCE: phplang's namespaces are flat, so while this constant is exact, the class and function names around it stay unqualified where the reference qualifies them.
namespace A\B; echo __NAMESPACE__; // => A\B
# __TRAIT__
__TRAIT__: string
The enclosing `trait` declaration's name, and `""` everywhere else — including inside a class that merely USES the trait, once the method is being read as part of that class's own body.
trait T { function t() { echo __TRAIT__; } } class C { use T; } (new C)->t(); // => T
Operator
# $$
$$name
${expr}
A variable VARIABLE: the operand is evaluated and its string value names the variable to read or write, so the name is not known until it runs. `$$x` nests, so `$$$x` is two lookups, and `${expr}` takes any expression. What it names is an ordinary variable — assignable, unsettable, and acceptable where a by-reference parameter wants a location. A quiet context reads through it without the undefined-variable warning.
$a = 5; $n = 'a'; echo $$n, ' ', ${'a'}; // => 5 5
expr + expr // binary; +expr // unary
Numeric addition, or array union when BOTH operands are arrays — the left operand's entries win and the right contributes only the keys it lacks. Under PHP 8 a string operand with no numeric prefix (`"g"`, `""`, `" "`) makes the whole operation a `TypeError: Unsupported operand types: string + int`, while one that merely trails garbage (`"5g"`) raises `Warning: A non-numeric value encountered` and continues with its prefix. Operands resolve left before right, so `"5g" + "g"` warns and then throws while `"g" + "5g"` throws without warning.
echo 2 + 3; // => 5
expr - expr // binary; -expr // unary
Numeric subtraction and arithmetic negation. Unary minus binds looser than `**`, so `-2 ** 2` is `-(2 ** 2)`. Both unary signs are multiplications to the engine, which is why `-"g"` and `+"g"` report `Unsupported operand types: string * int`.
echo 7 - 2, " ", -2 ** 2; // => 5 -4
expr * expr
Numeric multiplication. Integer operands stay integers unless the result overflows into a float.
echo 6 * 7; // => 42
expr / expr
Division. Two integers that divide exactly yield an integer, otherwise a float. A zero divisor throws a catchable `DivisionByZeroError` with the message `Division by zero`.
echo 7 / 2; // => 3.5
expr % expr
Integer remainder, taking the sign of the left operand. Both operands are cast to integers first, so `7.5 % 2` is `1`. A zero divisor throws a catchable `DivisionByZeroError` with the message `Modulo by zero`.
echo 7 % 3; // => 1
# **
expr ** expr
Exponentiation. It is right-associative and binds tighter than unary minus, and its right operand may itself be a unary expression, so `2 ** -1` parses.
echo 2 ** 3 ** 2; // => 512
expr . expr
String concatenation, casting both operands to strings. DIVERGENCE: it carries the SAME precedence as `+` and `-` (PHP 7 behaviour). PHP 8 lowered `.` below the additive operators, so `"x" . 1 + 2` groups as `("x" . 1) + 2` here and evaluates to `2`, where PHP 8 gives `"x3"`.
echo "a" . "b"; // => ab
echo "x" . 1 + 2; // => 2 (PHP 8 prints: x3)
target = expr
Assignment, right-associative and itself an expression. Arrays and objects are handles, so assigning one aliases the same storage rather than copying it — a divergence from PHP's copy-on-write arrays.
$a = 5; echo $a; // => 5
# =&
$target = &expr
Reference binding: the target becomes another name for the same storage cell rather than a copy of its value.
$a = 1; $b = &$a; $b = 9; echo $a; // => 9
# += -= *= /= %= .= **=
target op= expr
The arithmetic and string compound assignments. Each reads the target, applies the matching binary operator, and stores the result back — including that operator's PHP 8 operand rules, so `$x = "g"; $x += 9;` is a `TypeError` just as `"g" + 9` is.
$x = 10; $x += 5; $x .= "!"; echo $x; // => 15!
# &= |= ^= <<= >>=
target op= expr
The bitwise compound assignments — AND, OR, XOR, left shift, and right shift applied in place.
$m = 6; $m &= 3; echo $m; // => 2
# ??=
target ??= expr
Null-coalescing assignment: stores the right-hand value only when the target is null. It desugars to `target = (target ?? expr)`, so the target is read twice.
$a = null; $a ??= 5; echo $a; // => 5
# ==
expr == expr
Loose equality with PHP 8 string/number comparison: a numeric string compares numerically against a number, while a non-numeric string makes the number a string first — so `"abc" == 0` is false.
var_dump("10" == 10, "abc" == 0); // => bool(true) bool(false)
# !=
expr != expr // also spelled <>
Loose inequality — the negation of `==`. The legacy `<>` spelling lexes to the same operator.
var_dump(1 != 2, 1 <> 1); // => bool(true) bool(false)
# ===
expr === expr
Strict equality: values must share a type and, for arrays and objects, be the same handle. It is also the comparison `match` uses for its arms.
var_dump("10" === 10, 10 === 10); // => bool(false) bool(true)
# !==
expr !== expr
Strict inequality — the negation of `===`. `isset()` desugars to a chain of these against null.
var_dump("10" !== 10); // => bool(true)
# < > <= >=
expr < expr // and > <= >=
Relational comparison using the same loose rules as `==`: numeric where both sides look numeric, string comparison otherwise.
var_dump(2 < 10, "2" < "10"); // => bool(true) bool(true)
# <=>
expr <=> expr: int
The spaceship operator: `-1`, `0`, or `1` as the left operand sorts before, with, or after the right. It is the natural comparator for `usort`.
echo 10 <=> 3; // => 1
# && ||
expr && expr // expr || expr
Short-circuiting logical AND and OR. The right operand is not evaluated when the left already decides the result. Both yield a real boolean.
var_dump(true && false, false || true); // => bool(false) bool(true)
!expr: bool
Logical negation of the operand's truthiness. `empty($x)` desugars to exactly this.
var_dump(!0, !"a"); // => bool(true) bool(false)
# & | ^ ~
expr & expr // | ^ ; ~expr
Bitwise AND, OR, XOR, and one's-complement NOT over 64-bit integers. Both operands are cast to integers first. DIVERGENCE: `&`/`|`/`^` do not operate byte-wise on strings.
echo 6 & 3, " ", 6 | 3, " ", 6 ^ 3, " ", ~5; // => 2 7 5 -6
# << >>
expr << expr // expr >> expr
Arithmetic left and right shift on 64-bit integers. A shift count at or beyond 64 yields `0` for `<<` and the sign-fill (`0` or `-1`) for `>>` rather than wrapping.
echo 1 << 4, " ", -16 >> 2; // => 16 -4
# ++ --
++target target++ --target target--
Increment and decrement in prefix and postfix position. The prefix form evaluates to the new value, the postfix form to the old one. They apply to variables, array elements, properties, and static properties.
$i = 5; echo $i++, $i, ++$i; // => 567
# ?:
cond ? then : else
cond ?: else
The full ternary and the short `?:` (Elvis) form, which yields the condition itself when it is truthy.
echo 1 ? "a" : "b", "" ?: "fallback"; // => afallback
# ??
expr ?? expr
Null coalescing: the right operand is used when the left is null, and it is quiet about undefined variables and missing keys. Right-associative, so a chain falls through left to right.
$a = null; echo $a ?? "d"; // => d
# ->
$object->property $object->method(args)
Instance member access — property read/write and method call. The member name is a bareword; `$o->$name` is not parsed.
class C { public $x = 1; function f() { return 2; } }
$o = new C; echo $o->x, $o->f(); // => 12
# ?->
$object?->property $object?->method(args)
Nullsafe member access: yields null instead of raising when the left operand is null, short-circuiting the rest of the chain.
$o = null; var_dump($o?->x); // => NULL
# ::
Class::CONST Class::$staticProp Class::method(args) Class::class $cls::CONST
Scope resolution: class constants, static properties, static and parent method calls, and the `::class` name literal. The left side is either a bareword class name — `self`, `parent`, and `static` included — or a dereferenceable expression whose value names the class at run time: a string is the class name, an object contributes its own. A value that is neither is `Error: Class name must be a valid object or a string`, and a string naming nothing is `Error: Class "X" not found`. `::class` is stricter than the rest: it answers for an object only, and refuses even a class-name string with `TypeError: Cannot use "::class" on string`.
class A { const K = 1; static $s = 2; } $c = "A"; echo A::K, $c::$s, (new A)::class; // => 12A
# []
expr[index] $array[] = expr
Array and string indexing. A bare `$a[]` on the left of an assignment appends with the next integer key. String indexing is by byte offset and accepts a negative offset counting from the end.
$a = [1, 2]; $a[] = 3; echo $a[2], "abc"[-1]; // => 3c
# ...
f(...$array) function f(...$rest) strlen(...)
Four roles for one token: argument unpacking at a call site, unpacking inside an array literal (`[...$a, ...$b]`), a variadic parameter that collects the trailing arguments into an array, and — as the sole argument — PHP 8.1 first-class callable syntax, which builds a real `Closure`. Unpacking drives a Generator or a Traversable as well as an array; anything else raises "Only arrays and Traversables can be unpacked". DIVERGENCE: a string-keyed array unpacked at a CALL binds by position rather than by name.
function sum(...$n) { return array_sum($n); }
echo sum(...[1, 2, 3]); // => 6
$f = strlen(...); echo $f("abcd"); // => 4
# name:
f(paramName: value)
PHP 8.0 named arguments. An identifier followed by a single `:` inside an argument list binds that argument to the named parameter, so arguments may be supplied out of order.
function f($a, $b = "z") { return "$a$b"; } echo f(b: "q", a: 1); // => 1q
@expr
The error-suppression operator. The operand is evaluated exactly as it would be without the `@` and only the DIAGNOSTICS it raises are dropped — including the ones raised from inside the functions it calls, so `@preg_match('/[a', $s)` and `@range('ab', 'c')` are silent too. It is NOT an isset-mode read: `@$o->p` calls `__get` (where `isset($o->p)` would ask `__isset`), and it does not swallow an `Error`, so `@$o->privateProp` still throws. Suppression is restored when an exception unwinds out of the expression.
echo @$undefined ?? "quiet"; // => quiet
\Name Ns\Name
The namespace separator. Because phplang uses one flat namespace, a leading `\` is skipped and a qualified name folds to its last segment — `\RuntimeException` and `App\Util\RuntimeException` both resolve to `RuntimeException`.
try { throw new \Exception("x"); } catch (\Exception $e) { echo $e->getMessage(); } // => x
# $var "…$var…"
"text $var text" // double-quoted interpolation
Double-quoted strings and heredoc-free literals interpolate a bare `$name`. DIVERGENCE: the braced form `{$expr}` is NOT interpolated — the braces are emitted literally around the substituted variable — and heredoc/nowdoc (`<<<EOT`) is not lexed at all.
$s = "ab"; echo "v=$s"; // => v=ab
echo "{$s}"; // => {ab} (PHP 8 prints: ab)
Predefined constant
# PHP_INT_MAX
PHP_INT_MAX: int = 9223372036854775807
The largest representable integer. phplang integers are 64-bit signed, so this is `i64::MAX`.
echo PHP_INT_MAX; // => 9223372036854775807
# PHP_INT_MIN
PHP_INT_MIN: int = -9223372036854775808
The smallest representable integer, `i64::MIN`.
# PHP_INT_SIZE
PHP_INT_SIZE: int = 8
Integer width in bytes. Always 8 — phplang has no 32-bit build.
# PHP_FLOAT_DIG
PHP_FLOAT_DIG: int = 15
Number of decimal digits a float can round-trip.
# PHP_FLOAT_EPSILON
PHP_FLOAT_EPSILON: float = 2.220446049250313E-16
The smallest positive value that, added to 1.0, produces a different float (`f64::EPSILON`).
# PHP_FLOAT_MAX
PHP_FLOAT_MAX: float = 1.7976931348623157E+308
The largest finite float, `f64::MAX`.
# PHP_FLOAT_MIN
PHP_FLOAT_MIN: float = 2.2250738585072014E-308
The smallest positive normalized float, `f64::MIN_POSITIVE`.
# INF
INF: float = f64::INFINITY
Positive floating-point infinity. `is_infinite(INF)` is true.
# NAN
NAN: float = f64::NAN
The floating-point not-a-number value. It compares unequal to everything, including itself.
# PHP_VERSION
PHP_VERSION: string = "8.3.0"
The PHP language version phplang targets, reported as a fixed string.
echo PHP_VERSION; // => 8.3.0
# PHP_MAJOR_VERSION
PHP_MAJOR_VERSION: int = 8
Major component of `PHP_VERSION`.
# PHP_MINOR_VERSION
PHP_MINOR_VERSION: int = 3
Minor component of `PHP_VERSION`.
# PHP_RELEASE_VERSION
PHP_RELEASE_VERSION: int = 0
Release component of `PHP_VERSION`.
# PHP_VERSION_ID
PHP_VERSION_ID: int = 80300
The version as a single comparable integer, `major*10000 + minor*100 + release`.
# PHP_OS
PHP_OS: string = "Darwin" | "WINNT" | "Linux"
The host operating system, resolved at COMPILE time from `cfg!(target_os)` rather than by a runtime `uname(2)`.
# PHP_OS_FAMILY
PHP_OS_FAMILY: string = "Windows" | "Darwin" | "Linux"
The operating-system family, also decided at compile time. Every non-Windows, non-macOS target reports `Linux`.
# PHP_EOL
PHP_EOL: string = "\n"
The platform line terminator. DIVERGENCE: it is `\n` unconditionally, including on Windows builds where PHP uses `\r\n`.
echo "a" . PHP_EOL . "b"; // => a<newline>b
# DIRECTORY_SEPARATOR
DIRECTORY_SEPARATOR: string = "/" | "\\"
The path component separator, `\` on Windows builds and `/` everywhere else.
# PATH_SEPARATOR
PATH_SEPARATOR: string = ":" | ";"
The separator between entries in a path list, `;` on Windows builds and `:` everywhere else.
# PHP_ROUND_HALF_UP
PHP_ROUND_HALF_UP: int = 1
Rounding mode: halves away from zero. This is the behaviour `round()` always uses — the mode argument is not read.
# PHP_ROUND_HALF_DOWN
PHP_ROUND_HALF_DOWN: int = 2
Rounding mode: halves toward zero. Defined for source compatibility; `round()` ignores the mode argument.
# PHP_ROUND_HALF_EVEN
PHP_ROUND_HALF_EVEN: int = 3
Rounding mode: halves to the nearest even value. Honoured: `round(2.5, 0, PHP_ROUND_HALF_EVEN)` is `2` and `round(3.5, 0, PHP_ROUND_HALF_EVEN)` is `4`.
# PHP_ROUND_HALF_ODD
PHP_ROUND_HALF_ODD: int = 4
Rounding mode: halves to the nearest odd value. Honoured: `round(2.5, 0, PHP_ROUND_HALF_ODD)` is `3` and `round(3.5, 0, PHP_ROUND_HALF_ODD)` is `3`.
# E_ERROR
E_ERROR: int = 1
Error level for a fatal runtime error. phplang has no error-level machinery, so this is a value for scripts to pass around.
# E_WARNING
E_WARNING: int = 2
Error level for a non-fatal runtime warning.
# E_PARSE
E_PARSE: int = 4
Error level for a compile-time parse error.
# E_NOTICE
E_NOTICE: int = 8
Error level for a runtime notice.
# E_STRICT
E_STRICT: int = 2048
Error level for interoperability suggestions. The level was REMOVED in PHP 8.4 — nothing raises it and it is not part of `E_ALL` — but the constant remains so an existing `E_ALL & ~E_STRICT` still parses.
# E_DEPRECATED
E_DEPRECATED: int = 8192
Error level for a deprecation notice.
# E_ALL
E_ALL: int = 30719
Every error level that still exists. It is 30719, not the pre-8.4 32767: PHP 8.4 removed `E_STRICT` and took its bit out of `E_ALL`, so `E_ALL & E_STRICT` is 0. This is also the level a program starts at unless `-d error_reporting=…` says otherwise.
echo E_ALL, "|", E_ALL & E_STRICT; // => 30719|0
# E_USER_ERROR
E_USER_ERROR: int = 256
User-generated error level. Passed to `trigger_error()`, it prints `Fatal error: …` to stderr but does NOT halt the program.
# E_USER_WARNING
E_USER_WARNING: int = 512
User-generated warning level; `trigger_error()` labels it `Warning`.
# E_USER_NOTICE
E_USER_NOTICE: int = 1024
User-generated notice level and the default for `trigger_error()`, which labels it `Notice`.
# E_USER_DEPRECATED
E_USER_DEPRECATED: int = 16384
User-generated deprecation level; `trigger_error()` labels it `Deprecated`.
# SORT_REGULAR
SORT_REGULAR: int = 0
Sort flag for comparing items normally. phplang's sorts always compare this way — the flag argument is not read.
# SORT_NUMERIC
SORT_NUMERIC: int = 1
Sort flag for numeric comparison. Accepted by `array_multisort` as a column type marker; the plain sorts ignore it.
# SORT_STRING
SORT_STRING: int = 2
Sort flag for string comparison, with the same limited effect as `SORT_NUMERIC`.
# SORT_DESC
SORT_DESC: int = 3
Descending sort order for `array_multisort` columns.
# SORT_ASC
SORT_ASC: int = 4
Ascending sort order for `array_multisort` columns, and its default.
# SORT_LOCALE_STRING
SORT_LOCALE_STRING: int = 5
Locale-aware string sort flag. Defined for compatibility; phplang has no locale support.
# SORT_NATURAL
SORT_NATURAL: int = 6
Natural-order sort flag. Use `natsort()` for the real behaviour — the plain sorts do not read the flag.
# SORT_FLAG_CASE
SORT_FLAG_CASE: int = 8
Bit combined with `SORT_STRING` or `SORT_NATURAL` to fold case. Honoured: `sort($a, SORT_STRING|SORT_FLAG_CASE)` orders `["B", "a"]` as `["a", "B"]`.
# COUNT_NORMAL
COUNT_NORMAL: int = 0
`count()` mode counting only top-level elements. This is the only mode implemented.
# COUNT_RECURSIVE
COUNT_RECURSIVE: int = 1
`count()` mode that counts nested elements too: a nested array counts as one element AND contributes its own contents, at every depth. Recursion is into arrays only — a nested `Countable` is a single element and its own `count()` is not consulted.
echo count([1, [2, 3]], COUNT_RECURSIVE); // => 4
# STR_PAD_RIGHT
STR_PAD_RIGHT: int = 1
`str_pad()` / `mb_str_pad()` mode padding on the right. The default, and the fallback for an unrecognized mode.
# STR_PAD_LEFT
STR_PAD_LEFT: int = 0
`str_pad()` / `mb_str_pad()` mode padding on the left.
echo str_pad("5", 3, "0", STR_PAD_LEFT); // => 005
# STR_PAD_BOTH
STR_PAD_BOTH: int = 2
`str_pad()` / `mb_str_pad()` mode padding on both sides, with the left side floored.
# ARRAY_FILTER_USE_KEY
ARRAY_FILTER_USE_KEY: int = 2
`array_filter()` mode passing only the key to the callback.
# ARRAY_FILTER_USE_BOTH
ARRAY_FILTER_USE_BOTH: int = 1
`array_filter()` mode passing the value and then the key to the callback.
# PREG_PATTERN_ORDER
PREG_PATTERN_ORDER: int = 1
`preg_match_all()` ordering that groups results by capture group: `$matches[group][set]`. The default.
# PREG_SET_ORDER
PREG_SET_ORDER: int = 2
`preg_match_all()` ordering that groups results by match: `$matches[set][group]`. This bit is honoured.
# PREG_OFFSET_CAPTURE
PREG_OFFSET_CAPTURE: int = 256
Turns every `$matches` cell into a `[text, offset]` pair. The offset is a BYTE position, which is what PHP reports even under `/u`, where the subject is walked as UTF-8 but positions are still counted in bytes. A group that did not participate is `['', -1]`, not `['', 0]`. Honoured by `preg_match`, `preg_match_all` and `preg_replace_callback`; `preg_split` has its own separate `PREG_SPLIT_OFFSET_CAPTURE` bit.
# PREG_UNMATCHED_AS_NULL
PREG_UNMATCHED_AS_NULL: int = 512
Reports a non-participating group as null rather than an empty string, and SUPPRESSES the trailing-unmatched trim so the row keeps its full width. Combined with `PREG_OFFSET_CAPTURE` such a cell is `[null, -1]`.
# PREG_SPLIT_NO_EMPTY
PREG_SPLIT_NO_EMPTY: int = 1
`preg_split()` flag dropping empty pieces from the result. Honoured.
# PREG_SPLIT_DELIM_CAPTURE
PREG_SPLIT_DELIM_CAPTURE: int = 2
`preg_split()` flag interleaving captured delimiter groups into the result. Honoured.
# PREG_SPLIT_OFFSET_CAPTURE
PREG_SPLIT_OFFSET_CAPTURE: int = 4
`preg_split()` flag emitting `[piece, byteOffset]` pairs instead of bare strings. Honoured.
# JSON_HEX_TAG
JSON_HEX_TAG: int = 1
`json_encode()` flag escaping `<` and `>` as `\u003C` and `\u003E`. The hex digits are UPPER case, where the control and unicode escapes spell theirs in lower.
# JSON_HEX_AMP
JSON_HEX_AMP: int = 2
`json_encode()` flag escaping `&` as `\u0026`.
# JSON_HEX_APOS
JSON_HEX_APOS: int = 4
`json_encode()` flag escaping `'` as `\u0027`.
# JSON_HEX_QUOT
JSON_HEX_QUOT: int = 8
`json_encode()` flag escaping `"` as `\u0022` instead of `\"`.
# JSON_FORCE_OBJECT
JSON_FORCE_OBJECT: int = 16
`json_encode()` flag emitting a JSON object even for an array whose keys are already `0..n`.
# JSON_NUMERIC_CHECK
JSON_NUMERIC_CHECK: int = 32
`json_encode()` flag encoding a numeric string as the number it reads as, by PHP's own `is_numeric_string` — so `" 5"` and `"5 "` convert while `"0x1A"` does not. An integral result loses its fractional part (`"1e3"` encodes as `1000`), and a string that reads as a non-finite double (`"1e999"`) stays a string. Array KEYS are unaffected; JSON has no non-string key.
# JSON_UNESCAPED_SLASHES
JSON_UNESCAPED_SLASHES: int = 64
`json_encode()` flag leaving `/` unescaped, where the default writes `\/`.
# JSON_PRETTY_PRINT
JSON_PRETTY_PRINT: int = 128
`json_encode()` flag requesting indented output: four spaces per level, and `": "` rather than `":"` after a key.
echo json_encode(["a" => 1], JSON_PRETTY_PRINT); // => {
"a": 1
}
# JSON_UNESCAPED_UNICODE
JSON_UNESCAPED_UNICODE: int = 256
`json_encode()` flag emitting literal UTF-8 instead of `\uXXXX`.
# JSON_THROW_ON_ERROR
JSON_THROW_ON_ERROR: int = 4194304
Flag asking the JSON functions to throw `JsonException` instead of returning `false`/null. Honoured by `json_encode`, `json_decode` and `json_validate`: the exception's `getCode()` is the `JSON_ERROR_*` constant, and `json_last_error()` is left at `JSON_ERROR_NONE` because the failure travelled on the throw instead of being recorded for a later read.
# JSON_OBJECT_AS_ARRAY
JSON_OBJECT_AS_ARRAY: int = 1
`json_decode()` flag decoding JSON objects as PHP arrays. Consulted only when `$associative` is left null, which is `ext/json`'s own precedence — an explicit `$associative` wins over the flag in both directions.
# JSON_BIGINT_AS_STRING
JSON_BIGINT_AS_STRING: int = 2
`json_decode()` flag keeping oversized integers as strings. DIVERGENCE: not honoured — an integer beyond `i64` decodes to a float.
# JSON_ERROR_NONE
JSON_ERROR_NONE: int = 0
`json_last_error()` code meaning the last decode succeeded.
# JSON_ERROR_DEPTH
JSON_ERROR_DEPTH: int = 1
`json_last_error()` code meaning the maximum nesting depth was exceeded.
# JSON_ERROR_STATE_MISMATCH
JSON_ERROR_STATE_MISMATCH: int = 2
`json_last_error()` code for invalid or malformed JSON.
# JSON_ERROR_CTRL_CHAR
JSON_ERROR_CTRL_CHAR: int = 3
`json_last_error()` code for a raw control character inside a string.
# JSON_ERROR_SYNTAX
JSON_ERROR_SYNTAX: int = 4
`json_last_error()` code for a syntax error.
# JSON_ERROR_UTF8
JSON_ERROR_UTF8: int = 5
`json_last_error()` code for malformed UTF-8. The decoder also uses code 6 for an unpaired UTF-16 surrogate, which has no seeded constant.
# PHP_URL_SCHEME
PHP_URL_SCHEME: int = 0
`parse_url()`'s `$component` selector for the scheme (`https`). A component the URL does not carry reads back as null.
# PHP_URL_HOST
PHP_URL_HOST: int = 1
`parse_url()`'s `$component` selector for the host. A component the URL does not carry reads back as null.
# PHP_URL_PORT
PHP_URL_PORT: int = 2
`parse_url()`'s `$component` selector for the port, as an int. A component the URL does not carry reads back as null.
# PHP_URL_USER
PHP_URL_USER: int = 3
`parse_url()`'s `$component` selector for the userinfo user. A component the URL does not carry reads back as null.
# PHP_URL_PASS
PHP_URL_PASS: int = 4
`parse_url()`'s `$component` selector for the userinfo password. A component the URL does not carry reads back as null.
# PHP_URL_PATH
PHP_URL_PATH: int = 5
`parse_url()`'s `$component` selector for the path. A component the URL does not carry reads back as null.
# PHP_URL_QUERY
PHP_URL_QUERY: int = 6
`parse_url()`'s `$component` selector for the query string, without the `?`. A component the URL does not carry reads back as null.
# PHP_URL_FRAGMENT
PHP_URL_FRAGMENT: int = 7
`parse_url()`'s `$component` selector for the fragment, without the `#`. A component the URL does not carry reads back as null.
# INPUT_GET
INPUT_GET: int = 1
`filter_has_var()` input source for query parameters. phplang is standalone with no request context, so the lookup always reports false.
# INPUT_POST
INPUT_POST: int = 0
`filter_has_var()` input source for form-body parameters, with the same always-false result.
# FILTER_DEFAULT
FILTER_DEFAULT: int = 516
`filter_var()` filter that returns the value string-cast without validating. Also the fallback for any unrecognized filter id.
# FILTER_UNSAFE_RAW
FILTER_UNSAFE_RAW: int = 516
The same id as `FILTER_DEFAULT` — an unfiltered string-cast pass-through.
# FILTER_VALIDATE_INT
FILTER_VALIDATE_INT: int = 257
Validates an integer, honouring `min_range`/`max_range` options and the hex/octal flags. Leading zeros are rejected.
var_dump(filter_var("42", FILTER_VALIDATE_INT)); // => int(42)
# FILTER_VALIDATE_BOOLEAN
FILTER_VALIDATE_BOOLEAN: int = 258
Validates a boolean: `1/true/on/yes` are true, `0/false/off/no/""` are false, anything else fails.
# FILTER_VALIDATE_BOOL
FILTER_VALIDATE_BOOL: int = 258
The PHP 8 spelling of `FILTER_VALIDATE_BOOLEAN`, sharing its id and behaviour.
# FILTER_VALIDATE_FLOAT
FILTER_VALIDATE_FLOAT: int = 259
Validates a float. Every byte must be in `[0-9+-.eE]`, so `inf` and `nan` fail, and a non-finite result is rejected.
# FILTER_VALIDATE_REGEXP
FILTER_VALIDATE_REGEXP: int = 272
Validates against the `options['regexp']` pattern, compiled by the same engine pair `preg_match` uses — so the delimiter rules, the modifier set, look-around and backreferences all behave identically, and a pattern fault raises `Warning: filter_var(): <reason>` and sets `preg_last_error()` exactly as the reference does.
# FILTER_VALIDATE_DOMAIN
FILTER_VALIDATE_DOMAIN: int = 277
Validates a 1-253 byte domain, adding an RFC 1123 label check only when `FILTER_FLAG_HOSTNAME` is set.
# FILTER_VALIDATE_URL
FILTER_VALIDATE_URL: int = 273
Validates a URL, requiring both a scheme and a host.
# FILTER_VALIDATE_EMAIL
FILTER_VALIDATE_EMAIL: int = 274
Validates an email address of at most 320 bytes against a dotted-domain pattern.
var_dump(filter_var("a@b.co", FILTER_VALIDATE_EMAIL)); // => string(6) "a@b.co"
# FILTER_VALIDATE_IP
FILTER_VALIDATE_IP: int = 275
Validates an IP address with `std::net`, gated by the IPv4/IPv6 flags. The private- and reserved-range flags are not implemented.
# FILTER_VALIDATE_MAC
FILTER_VALIDATE_MAC: int = 276
Validates a MAC address in the 17-character `:`/`-` form or the 14-character `xxxx.xxxx.xxxx` form.
# FILTER_SANITIZE_STRING
FILTER_SANITIZE_STRING: int = 513
Strips `<…>` tags then encodes `"` and `'` as numeric entities. Sanitizers never fail.
# FILTER_SANITIZE_STRIPPED
FILTER_SANITIZE_STRIPPED: int = 513
The legacy alias of `FILTER_SANITIZE_STRING`, sharing its id.
# FILTER_SANITIZE_ENCODED
FILTER_SANITIZE_ENCODED: int = 514
URL-encoding sanitizer id. DIVERGENCE: not implemented — it falls through to the `FILTER_DEFAULT` pass-through.
# FILTER_SANITIZE_SPECIAL_CHARS
FILTER_SANITIZE_SPECIAL_CHARS: int = 515
Encodes `&`, `<`, `>`, `"`, and `'` as numeric character references.
# FILTER_SANITIZE_FULL_SPECIAL_CHARS
FILTER_SANITIZE_FULL_SPECIAL_CHARS: int = 522
Encodes the same five characters as named entities: `&`, `<`, `>`, `"`, `'`.
# FILTER_SANITIZE_EMAIL
FILTER_SANITIZE_EMAIL: int = 517
Keeps only characters legal in an email address, dropping everything else.
# FILTER_SANITIZE_URL
FILTER_SANITIZE_URL: int = 518
Keeps only characters legal in a URL.
# FILTER_SANITIZE_NUMBER_INT
FILTER_SANITIZE_NUMBER_INT: int = 519
Keeps only digits and the `+`/`-` signs.
# FILTER_SANITIZE_NUMBER_FLOAT
FILTER_SANITIZE_NUMBER_FLOAT: int = 520
Keeps digits and signs, plus `.`, `,`, and `e`/`E` when the matching ALLOW flag is set.
# FILTER_SANITIZE_ADD_SLASHES
FILTER_SANITIZE_ADD_SLASHES: int = 523
Backslash-escaping sanitizer id. DIVERGENCE: not implemented — it falls through to the `FILTER_DEFAULT` pass-through.
# FILTER_FLAG_ALLOW_OCTAL
FILTER_FLAG_ALLOW_OCTAL: int = 1
Lets `FILTER_VALIDATE_INT` accept an unsigned `0`-prefixed octal literal.
# FILTER_FLAG_ALLOW_HEX
FILTER_FLAG_ALLOW_HEX: int = 2
Lets `FILTER_VALIDATE_INT` accept an unsigned `0x`-prefixed hex literal.
# FILTER_FLAG_STRIP_LOW
FILTER_FLAG_STRIP_LOW: int = 4
Flag asking sanitizers to drop bytes below 0x20. DIVERGENCE: not implemented.
# FILTER_FLAG_STRIP_HIGH
FILTER_FLAG_STRIP_HIGH: int = 8
Flag asking sanitizers to drop bytes at or above 0x80. DIVERGENCE: not implemented.
# FILTER_FLAG_ALLOW_FRACTION
FILTER_FLAG_ALLOW_FRACTION: int = 4096
Lets `FILTER_SANITIZE_NUMBER_FLOAT` keep `.`.
# FILTER_FLAG_ALLOW_THOUSAND
FILTER_FLAG_ALLOW_THOUSAND: int = 8192
Lets `FILTER_SANITIZE_NUMBER_FLOAT` keep `,`, and lets `FILTER_VALIDATE_FLOAT` strip it before parsing.
# FILTER_FLAG_ALLOW_SCIENTIFIC
FILTER_FLAG_ALLOW_SCIENTIFIC: int = 16384
Lets `FILTER_SANITIZE_NUMBER_FLOAT` keep `e` and `E`.
# FILTER_FLAG_IPV4
FILTER_FLAG_IPV4: int = 1048576
Restricts `FILTER_VALIDATE_IP` to IPv4. It shares its integer with `FILTER_FLAG_HOSTNAME`, so passing it to `FILTER_VALIDATE_DOMAIN` silently enables the hostname check.
# FILTER_FLAG_IPV6
FILTER_FLAG_IPV6: int = 2097152
Restricts `FILTER_VALIDATE_IP` to IPv6. Setting neither family flag, or both, accepts either.
# FILTER_FLAG_HOSTNAME
FILTER_FLAG_HOSTNAME: int = 1048576
Adds an RFC 1123 label check to `FILTER_VALIDATE_DOMAIN`. Shares its integer with `FILTER_FLAG_IPV4`.
# FILTER_NULL_ON_FAILURE
FILTER_NULL_ON_FAILURE: int = 134217728
Makes a failing `filter_var()` return null instead of false. Honoured by every validating filter.
# FILTER_REQUIRE_SCALAR
FILTER_REQUIRE_SCALAR: int = 33554432
Flag requiring a scalar subject. Redundant here: an array subject already fails every filter.
# FILTER_REQUIRE_ARRAY
FILTER_REQUIRE_ARRAY: int = 16777216
Flag requiring an array subject. DIVERGENCE: not implemented.
# FILTER_FORCE_ARRAY
FILTER_FORCE_ARRAY: int = 67108864
Flag wrapping a scalar result in an array. DIVERGENCE: not implemented.
# MB_CASE_UPPER
MB_CASE_UPPER: int = 0
`mb_convert_case()` mode uppercasing the whole string. Also the fallback for any unrecognized mode.
# MB_CASE_LOWER
MB_CASE_LOWER: int = 1
`mb_convert_case()` mode lowercasing the whole string.
# MB_CASE_TITLE
MB_CASE_TITLE: int = 2
`mb_convert_case()` mode title-casing each run of letters. PHP's `_SIMPLE` variants (3-5) are not distinguished and fall into the uppercase branch.
# FILE_USE_INCLUDE_PATH
FILE_USE_INCLUDE_PATH: int = 1
Flag asking the file functions to search the include path. DIVERGENCE: not honoured — phplang has no include path.
# FILE_APPEND
FILE_APPEND: int = 8
`file_put_contents()` flag appending instead of truncating. This is the one flag that function honours.
# FILE_IGNORE_NEW_LINES
FILE_IGNORE_NEW_LINES: int = 2
`file()` flag stripping the trailing `\n` (and a preceding `\r`) from each line. Honoured.
# FILE_SKIP_EMPTY_LINES
FILE_SKIP_EMPTY_LINES: int = 4
`file()` flag dropping empty lines. Honoured.
# FILE_NO_DEFAULT_CONTEXT
FILE_NO_DEFAULT_CONTEXT: int = 16
Stream-context flag. Defined for compatibility; phplang has no stream contexts.
# LOCK_SH
LOCK_SH: int = 1
Shared advisory lock. DIVERGENCE: there is no `flock()` in this build and `file_put_contents` ignores `LOCK_EX`, so no locking happens anywhere.
# LOCK_EX
LOCK_EX: int = 2
Exclusive advisory lock. Accepted by `file_put_contents` and silently ignored.
# LOCK_UN
LOCK_UN: int = 3
Lock release. Defined for compatibility only.
# SCANDIR_SORT_ASCENDING
SCANDIR_SORT_ASCENDING: int = 0
`scandir()` sort order, ascending. The default; sorting is Rust byte order, not PHP's locale collation.
# SCANDIR_SORT_DESCENDING
SCANDIR_SORT_DESCENDING: int = 1
`scandir()` sort order, descending. Honoured.
# SCANDIR_SORT_NONE
SCANDIR_SORT_NONE: int = 2
`scandir()` mode leaving the entries unsorted. DIVERGENCE: not recognized — the result is always sorted.
# PATHINFO_DIRNAME
PATHINFO_DIRNAME: int = 1
`pathinfo()` selector returning just the directory component as a string.
# PATHINFO_BASENAME
PATHINFO_BASENAME: int = 2
`pathinfo()` selector returning just the final component.
# PATHINFO_EXTENSION
PATHINFO_EXTENSION: int = 4
`pathinfo()` selector returning the text after the last dot.
echo pathinfo("a/b.txt", PATHINFO_EXTENSION); // => txt
# PATHINFO_FILENAME
PATHINFO_FILENAME: int = 8
`pathinfo()` selector returning the basename with its extension removed.
# PATHINFO_ALL
PATHINFO_ALL: int = 15
`pathinfo()` selector returning the full associative array. This is what a call with no second argument produces.
# ENT_NOQUOTES
ENT_NOQUOTES: int = 0
HTML-entity flag leaving both quote characters alone. Read by `get_html_translation_table()`; `htmlspecialchars()` itself ignores its flags argument.
# ENT_COMPAT
ENT_COMPAT: int = 2
HTML-entity flag converting double quotes only.
# ENT_QUOTES
ENT_QUOTES: int = 3
HTML-entity flag converting both quote characters. The default for `get_html_translation_table()`.
# ENT_HTML401
ENT_HTML401: int = 0
Document-type bit selecting HTML 4.01. DIVERGENCE: the doctype bits are not read, so the single quote is always emitted as `'`.
# ENT_HTML5
ENT_HTML5: int = 48
Document-type bit selecting HTML 5. Not honoured.
# ENT_XML1
ENT_XML1: int = 16
Document-type bit selecting XML 1. Not honoured.
# ENT_XHTML
ENT_XHTML: int = 32
Document-type bit selecting XHTML. Not honoured.
# ENT_SUBSTITUTE
ENT_SUBSTITUTE: int = 8
Flag replacing invalid code unit sequences with U+FFFD. Not honoured; phplang strings are always valid UTF-8.
# ENT_IGNORE
ENT_IGNORE: int = 4
Flag discarding invalid code unit sequences. Not honoured, for the same reason as `ENT_SUBSTITUTE`.
# M_PI
M_PI: float = 3.141592653589793
The ratio of a circle's circumference to its diameter. `pi()` returns the same value.
echo round(M_PI, 4); // => 3.1416
# M_E
M_E: float = 2.718281828459045
Euler's number, the base of the natural logarithm.
# M_SQRT2
M_SQRT2: float = 1.4142135623730951
The square root of 2.
# M_SQRT1_2
M_SQRT1_2: float = 0.7071067811865476
The square root of 1/2.
# M_SQRT3
M_SQRT3: float = 1.7320508075688772
The square root of 3.
# M_2_SQRTPI
M_2_SQRTPI: float = 1.1283791670955126
Two divided by the square root of pi.
# M_SQRTPI
M_SQRTPI: float = 1.7724538509055159
The square root of pi.
# M_PI_2
M_PI_2: float = 1.5707963267948966
Pi divided by 2.
# M_PI_4
M_PI_4: float = 0.7853981633974483
Pi divided by 4.
# M_1_PI
M_1_PI: float = 0.3183098861837907
One divided by pi.
# M_2_PI
M_2_PI: float = 0.6366197723675814
Two divided by pi.
# M_LN2
M_LN2: float = 0.6931471805599453
The natural logarithm of 2.
# M_LN10
M_LN10: float = 2.302585092994046
The natural logarithm of 10.
# M_LOG2E
M_LOG2E: float = 1.4426950408889634
The base-2 logarithm of e.
# M_LOG10E
M_LOG10E: float = 0.4342944819032518
The base-10 logarithm of e.
# M_EULER
M_EULER: float = 0.5772156649015329
The Euler-Mascheroni constant.
Prelude class
# Exception
class Exception {
protected $message = ""; protected $code = 0; protected $previous = null;
protected $file = ""; protected $line = 0; protected $trace = "";
__construct($message = "", $code = 0, $previous = null)
getMessage(): string getCode(): int getPrevious(): ?Throwable
getFile(): string getLine(): int getTraceAsString(): string
__toString(): string
}
One of the two disjoint roots of the exception hierarchy. `catch (Throwable)` is special-cased in the host to match this root or `Error`. `file`, `line` and `trace` are recorded when the object is *constructed*, as the reference engine records them, so `getLine()` reports the `new` site even when the `throw` is on a later line. DIVERGENCE: `getTrace()` — the structured array form — is not declared; only the rendered `getTraceAsString()` is.
try { throw new Exception("boom", 7); }
catch (Exception $e) { echo $e->getMessage(), $e->getCode(); } // => boom7
# Error
class Error {
protected $message = ""; protected $code = 0; protected $previous = null;
protected $file = ""; protected $line = 0; protected $trace = "";
__construct($message = "", $code = 0, $previous = null)
getMessage(): string getCode(): int getPrevious(): ?Throwable
getFile(): string getLine(): int getTraceAsString(): string
__toString(): string
}
The other root of the hierarchy, for engine-level failures. It does NOT extend `Exception` — the two roots are disjoint, exactly as in PHP, so `catch (Exception)` never catches an `Error`. A zero divisor throws `DivisionByZeroError`, and a method call on a non-object throws `Error`. Reaching the top uncaught prints the reference's `Fatal error: Uncaught <Class>: <message> in <file>:<line>` block, stack trace and all, on stdout. DIVERGENCE: most other runtime failures — a bad standard-library argument, a call to an undefined function — still abort with a host-level `php: <message>` on stderr that no `catch` block can intercept.
try { throw new TypeError("t"); }
catch (Exception $e) { echo "E"; } catch (Error $e) { echo "R"; } // => R
# RuntimeException
class RuntimeException extends Exception {}
Thrown for an error only detectable at run time. Adds no members of its own.
# LogicException
class LogicException extends Exception {}
Thrown for an error in the program's logic that should be caught at development time.
# InvalidArgumentException
class InvalidArgumentException extends LogicException {}
Thrown when an argument does not match the expected type or value.
# ArithmeticError
class ArithmeticError extends Error {}
Thrown for an arithmetic failure, such as a shift by a negative count.
# DivisionByZeroError
class DivisionByZeroError extends ArithmeticError {}
The class raised for a zero divisor. `/` and `intdiv()` throw it with the message `Division by zero`, `%` with `Modulo by zero`, and a program may throw and catch it itself.
try { throw new DivisionByZeroError("x"); }
catch (ArithmeticError $e) { echo get_class($e); } // => DivisionByZeroError
# TypeError
class TypeError extends Error {}
Thrown when a value has the wrong type for the operation. phplang enforces no declared parameter types, so it is raised far less often than in PHP.
# ValueError
class ValueError extends Error {}
Thrown when a value is of the right type but outside the accepted domain — the class the standard library's range and argument errors report.
# UnhandledMatchError
class UnhandledMatchError extends Error {}
Thrown by a `match` expression whose subject matched no arm and which had no `default`. The message reads `Unhandled match case <subject>`.
try { echo match (5) { 1 => "a" }; }
catch (UnhandledMatchError $e) { echo $e->getMessage(); } // => Unhandled match case 5
# DateInterval
class DateInterval {
public $y, $m, $d, $h, $i, $s, $f, $invert, $days;
__construct($spec = "")
_seconds(): int format($fmt): string
}
A duration parsed from an ISO 8601 spec such as `P1Y2M3DT4H5M6S`, with `W` weeks folded into days. `_seconds()` is the phplang-specific helper `DateTime::add`/`sub` use, converting with the fixed approximations of 30 days per month and 365 days per year. `format()` handles `%y %m %d %h %i %s %a %R %%`. DIVERGENCE: `$days` is only set by `DateTime::diff`, and PHP's `createFromDateString` static is absent.
$i = new DateInterval("P1DT2H"); echo $i->d, $i->h; // => 12
# DateTime
class DateTime {
public $ts;
__construct($datetime = "now")
format($format) getTimestamp() setTimestamp($ts)
modify($modifier) setDate($y,$m,$d) setTime($h,$i,$s = 0)
add($interval) sub($interval) diff($other)
}
A mutable date/time built on the `date`/`strtotime`/`mktime` functions, so everything it computes is UTC. The mutators return `$this` for chaining. DIVERGENCE: it stores a plain integer timestamp in the public `$ts`, has no timezone parameter or `DateTimeZone` support, no sub-second precision, and none of PHP's `createFromFormat`/`setISODate`/`getTimezone` API.
$d = new DateTime("@0"); echo $d->format("Y-m-d"); // => 1970-01-01
# DateTimeImmutable
class DateTimeImmutable {
public $ts;
__construct($datetime = "now")
format($format) getTimestamp()
modify($modifier) add($interval) sub($interval)
}
The immutable counterpart of `DateTime`: `modify`, `add`, and `sub` each return a NEW instance and leave the receiver untouched. DIVERGENCE: it declares a smaller API than `DateTime` — there is no `setTimestamp`, `setDate`, `setTime`, or `diff` — and it does not share an interface with `DateTime`.
$a = new DateTimeImmutable("@0"); $b = $a->add(new DateInterval("P1D"));
echo $a->getTimestamp(), " ", $b->getTimestamp(); // => 0 86400
# stdClass
class stdClass {}
The empty generic object. Properties are added dynamically at assignment. Produced by `new stdClass` and by the `(object)` cast. DIVERGENCE: `json_decode` always yields arrays, so it never produces one.
$o = new stdClass; $o->x = 1; echo $o->x, get_class($o); // => 1stdClass
# SplDoublyLinkedList
class SplDoublyLinkedList {
public $dll = [];
push($v) pop() shift() unshift($v) top() bottom()
count() isEmpty() toArray()
offsetGet($i) offsetSet($i,$v) offsetExists($i) offsetUnset($i)
getIterator()
}
A list backed by the public `$dll` array. Because phplang arrays are reference-based handles, the methods alias the property to a local and mutate it in place. DIVERGENCE: it implements no interface, so `$list[0]` array syntax and `foreach ($list as …)` do not work — use `offsetGet()` and iterate `getIterator()`.
$l = new SplDoublyLinkedList; $l->push(1); $l->push(2);
echo $l->count(), $l->top(); // => 22
# SplStack
class SplStack extends SplDoublyLinkedList {}
A LIFO view over `SplDoublyLinkedList`, using `push()`/`pop()`/`top()`. It adds no members and does not reverse the iteration order.
$s = new SplStack; $s->push(1); $s->push(2); echo $s->pop(); // => 2
# SplQueue
class SplQueue extends SplDoublyLinkedList {
enqueue($v) dequeue()
}
A FIFO view over `SplDoublyLinkedList`: `enqueue()` appends and `dequeue()` removes from the front.
$q = new SplQueue; $q->enqueue(1); $q->enqueue(2); echo $q->dequeue(); // => 1
# SplFixedArray
class SplFixedArray {
public $data = []; public $sz = 0;
__construct($size = 0)
offsetGet($i) offsetSet($i,$v) offsetExists($i)
getSize() setSize($size) count() toArray() getIterator()
}
A fixed-length array pre-filled with nulls. DIVERGENCE: `offsetSet` performs no bounds check, so writing past `getSize()` grows the backing array instead of throwing `RuntimeException`, and `setSize()` only updates the recorded size without truncating or extending the storage.
$a = new SplFixedArray(3); $a->offsetSet(0, "x");
echo $a->getSize(), $a->offsetGet(0); // => 3x
# ArrayObject
class ArrayObject {
public $storage = [];
__construct($array = [])
offsetGet($k) offsetSet($k,$v) offsetExists($k) offsetUnset($k)
append($v) count() getArrayCopy() getIterator()
}
An object wrapper around an array. DIVERGENCE: `getArrayCopy()` returns the backing handle rather than a copy, so mutating the result also mutates the object; and because no interface is implemented, `$obj[$k]` and `foreach` over the object do not work.
$o = new ArrayObject(["a" => 1]); $o->append(2);
echo $o->count(), $o->offsetGet("a"); // => 21
# ArrayIterator
class ArrayIterator extends ArrayObject {}
An alias of `ArrayObject` with no added members. DIVERGENCE: it implements no `Iterator` interface and has none of PHP's `current`/`key`/`next`/`valid`/`rewind` cursor methods.
# SplObjectStorage
class SplObjectStorage {
public $store = [];
attach($obj, $data = null) detach($obj)
contains($obj) count()
}
A set of objects with optional attached data, keyed by `spl_object_id()`. DIVERGENCE: it has no `offsetGet`/`offsetSet` accessors, so the attached data cannot be read back, and it is neither countable nor iterable through the language's own syntax.
$s = new SplObjectStorage; $o = new stdClass; $s->attach($o);
var_dump($s->contains($o), $s->count()); // => bool(true) int(1)
# SplPriorityQueue
class SplPriorityQueue {
public $items = [];
insert($value, $priority) extract() top()
count() isEmpty() _best()
}
A priority queue storing `[priority, value]` pairs. DIVERGENCE: it is not a heap — `_best()` performs a LINEAR scan for the highest priority on every `top()`/`extract()`, making those operations O(n). Priorities are compared with `>`, so ties resolve to the earliest inserted element.
$q = new SplPriorityQueue; $q->insert("lo", 1); $q->insert("hi", 9);
echo $q->extract(); // => hi
# SplHeap
class SplHeap {
public $items = [];
insert($v) extract() top()
count() isEmpty() compare($a, $b) _best()
}
The base heap class. DIVERGENCE: it is concrete rather than abstract and stores an unordered list, so `_best()` linear-scans with `compare()` on every `top()`/`extract()`. Override `compare()` to change the ordering; the base implementation is `$a <=> $b`, i.e. max-first.
$h = new SplHeap; $h->insert(3); $h->insert(9); echo $h->extract(); // => 9
# SplMaxHeap
class SplMaxHeap extends SplHeap {
compare($a, $b): int // $a <=> $b
}
A heap whose `extract()` yields the largest element first.
$h = new SplMaxHeap; $h->insert(1); $h->insert(5); echo $h->extract(); // => 5
# SplMinHeap
class SplMinHeap extends SplHeap {
compare($a, $b): int // $b <=> $a
}
A heap whose `extract()` yields the smallest element first, achieved by reversing the base comparison.
$h = new SplMinHeap; $h->insert(5); $h->insert(1); echo $h->extract(); // => 1
Core library — strings
# strlen
strlen(string $string): int
Length of the string in BYTES, not characters — `strlen("héllo")` is 6. Use `mb_strlen()` for the codepoint count.
echo strlen("abc"), " ", strlen("héllo"); // => 3 6
# strtoupper
strtoupper(string $string): string
Uppercases the ASCII letters in the string, leaving every other byte alone — so `strtoupper("héllo")` is `"HéLLO"`, as in PHP 8. Use `mb_strtoupper` for the Unicode-aware form.
echo strtoupper("abc"); // => ABC
# strtolower
strtolower(string $string): string
Lowercases the string with Rust's full Unicode `to_lowercase`, with the same non-ASCII divergence from PHP as `strtoupper`.
echo strtolower("ABC"); // => abc
# ucfirst
ucfirst(string $string): string
Uppercases the first character and leaves the rest untouched. The conversion is full Unicode, so the first character may expand to several codepoints.
echo ucfirst("hello"); // => Hello
# lcfirst
lcfirst(string $string): string
Lowercases the first character and leaves the rest untouched.
echo lcfirst("Hello"); // => hello
# ucwords
ucwords(string $string): string
Uppercases the first letter of each word. Words are separated by whitespace only — the `$separators` argument PHP accepts is not read, so `-` and `.` do not start a new word.
echo ucwords("hello world-foo"); // => Hello World-foo
# trim
trim(string $string): string
Removes leading and trailing characters, defaulting to PHP's set `" \t\n\r\0\x0B"`. The optional `$characters` argument replaces that set and understands `a..z` inclusive ranges; a malformed range warns `trim(): Invalid '..'-range, …` and contributes nothing. Byte-oriented.
echo trim(" hi "); // => hi
# ltrim
ltrim(string $string): string
Removes leading whitespace only. The `$characters` argument is not read.
echo ltrim(" hi"); // => hi
# rtrim
rtrim(string $string): string
Removes trailing whitespace only. The `$characters` argument is not read.
echo rtrim("hi ") . "!"; // => hi!
# chop
chop(string $string): string
The historical alias of `rtrim`, sharing its match arm and therefore its behaviour exactly.
# str_repeat
str_repeat(string $string, int $times): string
Concatenates the string with itself `$times` times. A count of zero yields the empty string; a negative count throws `ValueError: str_repeat(): Argument #2 ($times) must be greater than or equal to 0`. When `strlen * $times + 32` overflows the address space the engine stops with the UNCATCHABLE fatal `Possible integer overflow in memory allocation (len * times + 32)`, as the reference does.
echo str_repeat("ab", 3); // => ababab
# strrev
strrev(string $string): string
Reverses the string by CHARACTER, not by byte. DIVERGENCE: PHP reverses bytes, which corrupts multibyte text; this version leaves each character intact.
echo strrev("abc"); // => cba
# wordwrap
wordwrap(string $string, int $width = 75, string $break = "\n", bool $cut = false): string
Wraps the text to `$width` columns, breaking at spaces. With `$cut` true a word longer than the width is split rather than left overlong; a zero `$width` with `$cut` true has no answer and raises `ValueError: wordwrap(): Argument #4 ($cut_long_words) cannot be true when argument #2 ($width) is 0`.
echo wordwrap("a b c", 3, "|", true); // => a b|c
# substr
substr(string $string, int $offset, ?int $length = null): string
A substring counted in CHARACTERS. A negative offset counts from the end and a negative length omits that many trailing characters. DIVERGENCE: PHP's `substr` uses byte offsets, so results differ on multibyte input. It never returns `false`.
echo substr("hello", 1, 3); // => ell
# strpos
strpos(string $haystack, string $needle): int|false
BYTE offset of the first occurrence at or after `$offset`, or `false` when absent. A negative `$offset` counts from the end; one outside `[-strlen, strlen]` raises `ValueError: strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)` rather than reporting no match. An empty needle returns the offset itself.
var_dump(strpos("hello", "l"), strpos("hello", "z")); // => int(2) bool(false)
# str_replace
str_replace(string $search, string $replace, string $subject): string
Replaces every occurrence of the search string. `$search`/`$replace` may be arrays (paired by position, or one replacement for every needle), an array `$subject` is processed element-wise, and the by-reference `$count` receives the number of replacements.
echo str_replace("a", "b", "aaa"); // => bbb
# str_split
str_split(string $string, int $length = 1): array
Splits the string into chunks of `$length` characters; the last chunk may be shorter.
echo str_split("ab")[0]; // => a
# str_pad
str_pad(string $string, int $length, string $pad = " ", int $type = STR_PAD_RIGHT): string
Pads the string to `$length` by cycling `$pad`. `STR_PAD_RIGHT`, `STR_PAD_LEFT`, and `STR_PAD_BOTH` are all honoured; a string already at least that long is returned unchanged.
echo str_pad("5", 3, "0"), " ", str_pad("5", 3, "0", STR_PAD_LEFT); // => 500 005
# str_contains
str_contains(string $haystack, string $needle): bool
True when the haystack contains the needle. An empty needle is always found.
var_dump(str_contains("hello", "ell")); // => bool(true)
# str_starts_with
str_starts_with(string $haystack, string $needle): bool
True when the haystack begins with the needle.
var_dump(str_starts_with("hello", "he")); // => bool(true)
# str_ends_with
str_ends_with(string $haystack, string $needle): bool
True when the haystack ends with the needle.
var_dump(str_ends_with("hello", "lo")); // => bool(true)
# number_format
number_format(float $num, int $decimals = 0, string $decimal_separator = ".", string $thousands_separator = ","): string
Formats a number with grouped thousands and a fixed number of decimals, rounding half away from zero.
echo number_format(1234.567, 2); // => 1,234.57
# htmlspecialchars
htmlspecialchars(string $string): string
Escapes `&`, `<`, `>`, and — as `$flags` selects — `"` (`ENT_COMPAT`) and `'` (`ENT_QUOTES`, the default since PHP 8.1, written as the numeric `'` under `ENT_HTML401`). DIVERGENCE: `$encoding` and `$double_encode` are not read, so `&` is always re-encoded.
echo htmlspecialchars("a<b"); // => a<b
# htmlentities
htmlentities(string $string): string
`htmlspecialchars` plus the full HTML 4.01 named-entity table, so `é` becomes `é`, `€` `€`, and `α` `α`. `$flags` selects quote handling as it does there. DIVERGENCE: `$encoding` and `$double_encode` are not read.
echo htmlentities("café"); // => café
# strcmp
strcmp(string $string1, string $string2): int
Byte comparison returning only the sign: `-1`, `0`, or `1`. PHP 8 also normalizes to the sign, so this matches.
echo strcmp("a", "b"); // => -1
# strcasecmp
strcasecmp(string $string1, string $string2): int
Compares after lowercasing both operands with full Unicode folding, returning `-1`, `0`, or `1`.
echo strcasecmp("A", "a"); // => 0
# strncmp
strncmp(string $string1, string $string2, int $length): int
Compares at most the first `$length` BYTES of each string, returning the sign. A length beyond either string simply compares what is there.
echo strncmp("abcd", "abzz", 2); // => 0
# substr_compare
substr_compare(string $haystack, string $needle, int $offset, ?int $length = null, bool $case_insensitive = false): int
Compares `$needle` against the slice of `$haystack` starting at `$offset` (negatives count from the end), over `$length` bytes or, when that is null, `max(strlen($needle), strlen($haystack) - $offset)`. The result is NOT normalized: differing bytes answer their signed difference, and only a tie on content falls back to -1/0/1 on length. An `$offset` past the haystack, or a negative `$length`, throws `ValueError`; a `$length` of 0 answers 0.
echo substr_compare("Hello", "llo", 2); // => 0
# chr
chr(int $codepoint): string
The one-character string for a byte value, reduced modulo 256 so any integer is accepted — a value outside `0..=255` raises `Deprecated: chr(): Providing a value not in-between 0 and 255 is deprecated…` first. DIVERGENCE: the result is that CODEPOINT, so `chr(233)` yields a two-byte UTF-8 `é` rather than PHP's single 0xE9 byte.
echo chr(65); // => A
# ord
ord(string $character): int
The value of the first BYTE of the string, so a multibyte character reports its leading byte (`ord("é")` is 195). The empty string returns 0 and raises `Deprecated: ord(): Providing an empty string is deprecated`.
echo ord("A"); // => 65
# dechex
dechex(int $num): string
Lowercase hexadecimal rendering of an integer. DIVERGENCE: a negative number renders as `-` plus the magnitude, where PHP renders the unsigned 64-bit two's-complement form.
echo dechex(255); // => ff
# hexdec
hexdec(string $hex_string): int
The integer value of a hexadecimal string, after trimming surrounding whitespace. A leading `0x`/`0X` is dropped. Characters that are not hex digits are skipped, and skipping ANY raises `Deprecated: Invalid characters passed for attempted conversion, these have been ignored` before returning. The result is an integer when it fits `PHP_INT_MAX` and a float otherwise.
echo hexdec("ff"); // => 255
# bin2hex
bin2hex(string $string): string
Two lowercase hex digits per byte of the string's UTF-8 encoding.
echo bin2hex("AB"); // => 4142
# sprintf
sprintf(string $format, mixed ...$values): string
Formats the arguments into a string. Supports the `%b %c %d %u %e %E %f %F %g %G %o %s %x %X %%` conversions with width, precision, `-` left-align, `0` and `'` custom padding, `+` sign, and `%n$` argument reordering.
echo sprintf("%05.2f|%s|%x|%b", 3.14159, "s", 255, 5); // => 03.14|s|ff|101
Core library — arrays
# count
count(Countable|array $value, int $mode = COUNT_NORMAL): int
Number of elements in an array, or what a `Countable`'s own `count()` returns. Anything else is a `TypeError` — PHP 8 stopped answering 1 for a scalar. `$mode` is `COUNT_NORMAL` or `COUNT_RECURSIVE`, and any other value is a `ValueError`.
echo count([1, 2, 3]); // => 3
# sizeof
sizeof(Countable|array $value, int $mode = COUNT_NORMAL): int
The alias of `count`, sharing its match arm and therefore its modes and its errors exactly.
# array_keys
array_keys(array $array): array
A new list of the array's keys in insertion order. DIVERGENCE: the `$filter_value` and `$strict` arguments are not read, so the filtered form is unavailable.
echo implode(",", array_keys(["a" => 1, "b" => 2])); // => a,b
# array_values
array_values(array $array): array
A new list of the array's values, reindexed from 0.
echo implode(",", array_values([5 => "a", 9 => "b"])); // => a,b
# array_push
array_push(array $array, mixed ...$values): int
Appends each value with the next integer key and returns the new element count. The array is a shared handle, so the caller's variable sees the change.
$a = [1]; array_push($a, 2); echo implode(",", $a); // => 1,2
# in_array
in_array(mixed $needle, array $haystack, bool $strict = false): bool
True when the value occurs in the array, comparing loosely by default and with `===` when `$strict` is truthy.
var_dump(in_array("2", [1, 2, 3], true)); // => bool(false)
# range
range(mixed $start, mixed $end, int|float $step = 1): array
An inclusive sequence, ported from `PHP_FUNCTION(range)`. `$step` is validated FIRST and independently of the bounds, with a distinct message for each fault: `cannot be 0`, `must be greater than -9223372036854775808`, `must be a finite number, NAN|INF provided`, `must be greater than 0 for increasing ranges`, and `must be less than the range spanned by argument #1 ($start) and argument #2 ($end)`. Each bound is then classified: a one-byte NUMERIC string is ambiguous and is read as a character only when the other bound is also a string, so `range("1", "3")` yields strings while `range("1.5", "3")` yields floats. A character range walks BYTES using only the first byte of each bound (a longer bound warns `must be a single byte, subsequent bytes are ignored`), an empty string warns `must not be empty, casted to 0`, and a whole-valued float step keeps an int range int. A span too large for a hash table raises `The supplied range exceeds the maximum array size by N elements: …`.
echo implode(",", range(1, 4)), " ", implode("", range("a", "c")); // => 1,2,3,4 abc
# array_merge
array_merge(array ...$arrays): array
Concatenates the arrays. Integer keys are renumbered from 0 and string keys are overwritten by later arrays.
echo implode(",", array_merge([1], [2, 3])); // => 1,2,3
# array_map
array_map(?callable $callback, array $array, array ...$arrays): array
Applies the callback to each element and returns the results. With several arrays it walks them in parallel, and a null callback zips them into tuples.
echo implode(",", array_map("strtoupper", ["a", "b"])); // => A,B
# array_filter
array_filter(array $array, ?callable $callback = null, int $mode = 0): array
Keeps the elements for which the callback is truthy, PRESERVING keys. With no callback it keeps the truthy values. `$mode` selects what the callback receives: the value (0), the value and the key (`ARRAY_FILTER_USE_BOTH`), or the key alone (`ARRAY_FILTER_USE_KEY`).
echo implode(",", array_filter([0, 1, 2])); // => 1,2
# array_reduce
array_reduce(array $array, callable $callback, mixed $initial = null): mixed
Folds the array to a single value, calling `$callback($carry, $item)` for each element.
echo array_reduce([1, 2, 3], fn($c, $x) => $c + $x, 0); // => 6
# array_slice
array_slice(array $array, int $offset, ?int $length = null, bool $preserve_keys = false): array
Extracts a run of elements. A negative offset counts from the end and a negative length stops that many elements from the end.
echo implode(",", array_slice([1, 2, 3, 4], 1, 2)); // => 2,3
# array_reverse
array_reverse(array $array, bool $preserve_keys = false): array
A new array with the elements in reverse order. String keys are always preserved; integer keys are renumbered unless `$preserve_keys` is truthy.
echo implode(",", array_reverse([1, 2, 3])); // => 3,2,1
# array_sum
array_sum(array $array): int|float
Sum of the values as numbers. The result is an integer while every addend is one, and a float as soon as any is. An entry the `+` operator would reject warns `array_sum(): Addition is not supported on type <type>`; an array or an object with no numeric cast then contributes nothing, while a non-numeric string keeps the pre-8 behaviour of counting as 0.
var_dump(array_sum([1, 2.5])); // => float(3.5)
# array_product
array_product(array $array): int|float
Product of the values as numbers, following the same integer-until-a-float rule as `array_sum`, and the same `Multiplication is not supported on type <type>` warning for an entry `*` would reject — which is why `array_product([2, "a"])` is 0, the non-numeric string counting as a factor of 0. An empty array yields 1.
echo array_product([2, 3, 4]); // => 24
# array_flip
array_flip(array $array): array
Swaps keys and values. A later duplicate value overwrites the entry the earlier one produced.
echo array_flip(["a", "b"])["a"]; // => 0
# array_unique
array_unique(array $array): array
Keeps the first occurrence of each distinct value, preserving keys. DIVERGENCE: the `$flags` argument is not read, so the comparison mode is fixed.
echo implode(",", array_unique([1, 1, 2])); // => 1,2
# array_key_exists
array_key_exists(mixed $key, array $array): bool
True when the key is present, even if its value is null — the difference from `isset()`.
var_dump(array_key_exists("a", ["a" => null]), isset(["a" => null]["a"])); // => bool(true) bool(false)
# key_exists
key_exists(mixed $key, array $array): bool
The alias of `array_key_exists`, sharing its match arm.
# array_search
array_search(mixed $needle, array $haystack, bool $strict = false): mixed
The key of the first matching value, or `false` when none matches. `$strict` selects `===` over loose comparison.
echo array_search(2, [1, 2, 3]); // => 1
# sort
sort(array $array): bool
Sorts ascending in place and reindexes the keys from 0. The `$flags` argument selects the comparison (`SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_NATURAL`, optionally `|SORT_FLAG_CASE`). The sort is stable, as PHP 8's is.
$a = [3, 1, 2]; sort($a); echo implode(",", $a); // => 1,2,3
# rsort
rsort(array $array): bool
Sorts descending in place and reindexes the keys from 0.
$a = [1, 3, 2]; rsort($a); echo implode(",", $a); // => 3,2,1
# asort
asort(array $array): bool
Sorts by value ascending while preserving the key association.
$a = ["b" => 2, "a" => 1]; asort($a); echo implode(",", array_keys($a)); // => a,b
# arsort
arsort(array $array): bool
Sorts by value descending while preserving the key association.
$a = ["a" => 1, "b" => 2]; arsort($a); echo implode(",", array_keys($a)); // => b,a
# ksort
ksort(array $array): bool
Sorts by key ascending, keeping each key with its value.
$a = ["b" => 1, "a" => 2]; ksort($a); echo implode(",", array_keys($a)); // => a,b
# krsort
krsort(array $array): bool
Sorts by key descending.
$a = ["a" => 1, "b" => 2]; krsort($a); echo implode(",", array_keys($a)); // => b,a
# array_fill
array_fill(int $start_index, int $count, mixed $value): array
An array of `$count` copies of the value, keyed from `$start_index` upward. A negative `$count` raises `ValueError: array_fill(): Argument #2 ($count) must be greater than or equal to 0` and one past `INT_MAX` raises `… is too large`; a `$start_index` high enough that the last key would pass `PHP_INT_MAX` raises `Error: Cannot add element to the array as the next element is already occupied`, before any element is written.
echo implode(",", array_fill(0, 3, "x")); // => x,x,x
# array_combine
array_combine(array $keys, array $values): array
Pairs one array's values as keys with another's as values. Mismatched lengths throw `ValueError: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements`.
echo array_combine(["a"], [1])["a"]; // => 1
# array_diff
array_diff(array $array, array ...$excludes): array
The entries of the first array whose VALUE (compared by string form) appears in none of the others. Keys are preserved.
echo implode(",", array_diff([1, 2, 3], [2])); // => 1,3
# array_intersect
array_intersect(array $array, array ...$others): array
The entries of the first array whose value appears in every other array, compared by string form. Keys are preserved.
echo implode(",", array_intersect([1, 2, 3], [2, 3, 4])); // => 2,3
# implode
implode(string $separator, array $array): string
implode(array $array): string
Joins the array's values into a string. Passing only the array joins with no separator.
echo implode(",", [1, 2, 3]); // => 1,2,3
# join
join(string $separator, array $array): string
The alias of `implode`, sharing its match arm.
# explode
explode(string $separator, string $string): array
Splits the string on every occurrence of the separator. A positive `$limit` caps the number of parts with the last holding the remainder, a negative one drops that many parts off the end, and `0` behaves as `1`. An empty separator returns a single-element array rather than raising `ValueError`.
echo explode(",", "a,b")[1]; // => b
Core library — types and output
# settype
settype(mixed &$var, string $type): bool
Converts `$var` in place to `$type` and returns true. Accepts `bool`/`boolean`, `int`/`integer`, `float`/`double`, `string`, `array`, `object` and `null`; the conversion is the one the matching cast performs, so a non-numeric string becomes `0` and a scalar becomes a one-element array. Any other type name is a `ValueError`.
$n = "12abc"; settype($n, "integer"); var_dump($n); // => int(12)
# gettype
gettype(mixed $value): string
The legacy type name: `boolean`, `integer`, `double`, `string`, `array`, `object`, `NULL`, or `unknown type`. Use `get_debug_type()` for the modern short names.
echo gettype(1.0), "|", gettype(null); // => double|NULL
# is_array
is_array(mixed $value): bool
True when the value is an array. Arrays and objects share one handle representation, so this asks the host which kind the handle points at.
var_dump(is_array([1])); // => bool(true)
# is_int
is_int(mixed $value): bool
True only for a real integer — a numeric string is not one.
var_dump(is_int(5), is_int("5")); // => bool(true) bool(false)
# is_integer
is_integer(mixed $value): bool
The alias of `is_int`, sharing its match arm.
# is_long
is_long(mixed $value): bool
The second alias of `is_int`, sharing the same arm.
# is_float
is_float(mixed $value): bool
True only for a real float value.
var_dump(is_float(1.5)); // => bool(true)
# is_double
is_double(mixed $value): bool
The alias of `is_float`, sharing its match arm.
# is_string
is_string(mixed $value): bool
True only for a string value.
var_dump(is_string("x")); // => bool(true)
# is_bool
is_bool(mixed $value): bool
True only for `true` or `false`.
var_dump(is_bool(true), is_bool(1)); // => bool(true) bool(false)
# is_null
is_null(mixed $value): bool
True for null, which is also what an unset variable and a missing array key read as.
var_dump(is_null(null)); // => bool(true)
# is_numeric
is_numeric(mixed $value): bool
True for an integer, a float, or a string that parses as a number — including leading whitespace, a sign, hex-free decimals, and exponent notation.
var_dump(is_numeric("3.5"), is_numeric("3.5px")); // => bool(true) bool(false)
# is_callable
is_callable(mixed $value): bool
True for a closure handle, and for ANY non-empty string. DIVERGENCE: the host exposes no function-table lookup here, so `is_callable("no_such_function")` is true — the name is never resolved.
var_dump(is_callable("strlen"), is_callable("nope")); // => bool(true) bool(true)
# intval
intval(mixed $value): int
The integer value, taking the leading numeric prefix of a string and yielding 0 when there is none. DIVERGENCE: the `$base` argument is not read, so `intval("ff", 16)` is 0.
echo intval("42px"); // => 42
# floatval
floatval(mixed $value): float
The float value, taking the leading numeric prefix of a string.
echo floatval("3.5kg"); // => 3.5
# doubleval
doubleval(mixed $value): float
The alias of `floatval`, sharing its match arm.
# strval
strval(mixed $value): string
The string value. DIVERGENCE: an object stringifies to `Array` because `__toString` is never consulted.
echo strval(42); // => 42
# __cast_array
__cast_array(mixed $value): array
Internal target of the `(array)` cast — not a PHP function, and not meant to be called by name. An array passes through, `null` becomes the empty array, an object yields its properties keyed by name, and any other scalar becomes a one-element list. DIVERGENCE: PHP mangles private/protected property names in an object cast (`\0Class\0prop`, `\0*\0prop`); this returns the plain names.
var_dump((array)"a"); // => array(1) { [0]=> string(1) "a" }
# __cast_object
__cast_object(mixed $value): object
Internal target of the `(object)` cast — not a PHP function, and not meant to be called by name. An array becomes a `stdClass` whose keys are properties, `null` an empty `stdClass`, and any other scalar a `stdClass` with the value in a `scalar` property. An object passes through unchanged.
$o = (object)["a" => 1]; echo $o->a; // => 1
# boolval
boolval(mixed $value): bool
The boolean value. Falsey values are `0`, `0.0`, `""`, `"0"`, the empty array, and null.
var_dump(boolval(""), boolval("0"), boolval("a")); // => bool(false) bool(false) bool(true)
# print_r
print_r(mixed $value, bool $return = false): string|true
Renders a value in PHP's indented human-readable form. With `$return` truthy the text is returned instead of printed. A structure that contains itself prints its head and then ` *RECURSION*` in place of the repeated block.
echo print_r([1, 2], true); // => Array\n(\n [0] => 1\n [1] => 2\n)
# var_dump
var_dump(mixed ...$values): void
Prints each value with its type and structure — `bool(true)`, `int(3)`, `float(1.5)`, `string(3) "abc"`, and nested `array(n) { … }`. A repeated element in a self-referential structure is replaced entirely by `*RECURSION*`, type header included.
var_dump(true); // => bool(true)
# var_export
var_export(mixed $value, bool $return = false): string|null
Renders a value as parsable PHP source. With `$return` truthy the text is returned; otherwise it is printed and null is returned. A circular reference raises `Warning: var_export does not handle circular references` and writes `NULL` in that position, so the output stays parsable but no longer rebuilds the original.
var_export(1); // => 1
# printf
printf(string $format, mixed ...$values): int
Writes a `sprintf`-formatted string to output and returns its length in BYTES.
printf("%d", 7); // => 7
# json_encode
json_encode(mixed $value): string
Encodes a value as JSON, escaping `/` and non-ASCII characters by default and returning `false` (with `json_last_error()` set) for a NAN or INF (`JSON_ERROR_INF_OR_NAN`) or a structure containing itself (`JSON_ERROR_RECURSION`). `JSON_PRETTY_PRINT`, `JSON_UNESCAPED_SLASHES`, `JSON_UNESCAPED_UNICODE` and `JSON_THROW_ON_ERROR` are honoured. DIVERGENCE: the `$depth` argument is not read. `json_decode` lives in the `json` module, not here.
echo json_encode([1, 2, 3]); // => [1,2,3]
Strings
# substr_count
substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null): int
Counts non-overlapping matches inside the byte window `[$offset, $offset + $length)`. Negative offsets and lengths count from the end. An empty needle throws `ValueError: substr_count(): Argument #2 ($needle) must not be empty`. The window is a raw byte slice, so a multibyte haystack can be cut mid-character.
echo substr_count("aaa", "aa"); // => 1
# substr_replace
substr_replace(array|string $string, array|string $replace, array|int $offset, array|int|null $length = null): string|array
Splices the replacement over the byte range starting at `$offset`. An array `$string` returns an array spliced element-wise, with `$replace`, `$offset` and `$length` consumed positionally and falling back to `""` / `0` / to-the-end once exhausted. The result is rebuilt lossily, so splicing at a non-character boundary yields U+FFFD.
echo substr_replace("Hello", "XY", 1, 3); // => HXYo
# addcslashes
addcslashes(string $string, string $characters): string
Backslash-escapes every byte listed in `$characters`, which may use `a..z` inclusive ranges. Outside printable ASCII the escape is the C mnemonic (`\n`, `\t`, `\r`, `\a`, `\v`, `\b`, `\f`) or a three-digit octal. A malformed range warns `addcslashes(): Invalid '..'-range, …` and contributes nothing.
echo addcslashes("foo[bar]", "A..z"); // => \f\o\o\[\b\a\r\]
# stripcslashes
stripcslashes(string $string): string
The inverse of `addcslashes`: understands the C mnemonics plus `\xHH` (one or two hex digits) and `\NNN` (up to three octal digits). An unrecognized escape yields the escaped character itself, so `\z` is `z`.
echo stripcslashes('\101\x42'); // => AB
# count_chars
count_chars(string $string, int $mode = 0): array|string
Per-byte histogram. Mode 0 reports all 256 counters, 1 only the non-zero ones, 2 only the zero ones; modes 3 and 4 return a STRING of the bytes that did / did not occur. A `$mode` outside 0-4 throws `ValueError`. DIVERGENCE: modes 3 and 4 can name bytes above 0x7F, which this engine's UTF-8 strings widen to two bytes each (see BUGS.md).
print_r(count_chars("aab", 3)); // => ab
# strtok
strtok(string $string, ?string $token = null): string|false
Stateful tokenizer. Two arguments install a new subject and answer its first token; one argument continues the saved subject with a (possibly different) delimiter set. Running out of tokens answers `false` AND discards the subject, so a further one-argument call keeps answering `false` rather than restarting.
echo strtok("a b", " "), strtok(" "); // => ab
# strtr
strtr(string $string, array $pairs): string
strtr(string $string, string $from, string $to): string
Two-argument form: longest-key-first, non-overlapping substring replacement in a single left-to-right pass, dropping empty keys. Three-argument form: byte-wise translation with both sets truncated to the shorter length. DIVERGENCE: in the two-argument form an unmatched byte at or above 0x80 is re-encoded as its Latin-1 codepoint, so untouched non-ASCII text is mangled.
echo strtr("hi all", ["hi" => "bye"]); // => bye all
# strstr
strstr(string $haystack, string $needle, bool $before_needle = false): string|false
The part of the haystack from the first match onward, or the part before it when `$before_needle` is truthy; `false` when absent. An empty needle matches at offset 0 (PHP 8 semantics).
echo strstr("user@example.com", "@"); // => @example.com
# strchr
strchr(string $haystack, string $needle, bool $before_needle = false): string|false
The alias of `strstr`, sharing its match arm — the same alias relationship PHP has.
# stristr
stristr(string $haystack, string $needle, bool $before_needle = false): string|false
Case-insensitive `strstr`. DIVERGENCE: the folding is ASCII-only, so non-ASCII letters never match case-insensitively.
echo stristr("HELLO", "ell"); // => ELLO
# strrchr
strrchr(string $haystack, string $needle): string|false
The substring from the LAST occurrence of the needle's FIRST BYTE through the end; `false` when absent or the needle is empty. DIVERGENCE: PHP 8.3's `$before_needle` parameter is not read.
echo strrchr("a/b/c", "/"); // => /c
# strpbrk
strpbrk(string $string, string $characters): string|false
The substring beginning at the first byte that belongs to the character set, through the end; `false` when no byte matches.
echo strpbrk("This is a test", "st"); // => s is a test
# strspn
strspn(string $string, string $characters, int $offset = 0, ?int $length = null): int
Length in bytes of the initial run within the window whose bytes all belong to the set. Set membership is byte-oriented, so a multibyte mask character is matched byte by byte.
echo strspn("42 apples", "0123456789"); // => 2
# strcspn
strcspn(string $string, string $characters, int $offset = 0, ?int $length = null): int
The complement of `strspn`: the length of the initial run of bytes NOT in the set. It shares the same match arm with the membership test inverted.
echo strcspn("abc123", "0123456789"); // => 3
# stripos
stripos(string $haystack, string $needle, int $offset = 0): int|false
Byte offset of the first ASCII-case-insensitive match at or after the offset, or `false`. A negative offset counts from the end and an empty needle returns the offset. DIVERGENCE: folding is ASCII-only.
echo stripos("Hello", "L"); // => 2
# strrpos
strrpos(string $haystack, string $needle, int $offset = 0): int|false
Byte offset of the LAST match. A non-negative offset requires the match to start at or after it; a negative one caps how late it may start. An empty needle returns the string length (PHP 8 semantics).
echo strrpos("hello", "l"); // => 3
# strripos
strripos(string $haystack, string $needle, int $offset = 0): int|false
`strrpos` with ASCII case-insensitive comparison; it shares the same match arm. DIVERGENCE: folding is ASCII-only.
echo strripos("HELLO", "l"); // => 3
# strncasecmp
strncasecmp(string $string1, string $string2, int $length): int
Compares the first `$length` bytes after ASCII-lowercasing, returning `-1`, `0`, or `1`. A negative length raises `strncasecmp(): Argument #3 ($length) must be greater than or equal to 0`.
echo strncasecmp("Hello", "HELLO world", 5); // => 0
# str_ireplace
str_ireplace(array|string $search, array|string $replace, string $subject): string
ASCII-case-insensitive `str_replace`. An array `$search` is applied in sequence, index-matched against an array `$replace` (missing entries become `""`) or with one scalar for all. DIVERGENCE: an array `$subject` is string-cast rather than mapped, and there is no `$count` out-parameter.
echo str_ireplace("WORLD", "there", "Hello world"); // => Hello there
# nl2br
nl2br(string $string): string
Inserts a `<br />` before each `\r\n`, `\n\r`, `\r`, or `\n`, keeping the original break. DIVERGENCE: the `$use_xhtml` argument is not read, so the output is always the XHTML `<br />`.
echo nl2br("a\nb"); // => a<br />\nb
# chunk_split
chunk_split(string $string, int $length = 76, string $separator = "\r\n"): string
Appends the separator after every `$length` units. DIVERGENCE: chunking counts CODEPOINTS, not bytes as PHP does, so multibyte input chunks differently. A non-positive length throws `ValueError: chunk_split(): Argument #2 ($length) must be greater than 0`.
echo chunk_split("abcdef", 2, "-"); // => ab-cd-ef-
# quotemeta
quotemeta(string $string): string
Backslash-escapes each of `. \ + * ? [ ^ ] $ ( )`. The escape set is ASCII, so the result matches PHP's byte behaviour.
echo quotemeta("1+1=2"); // => 1\+1=2
# addslashes
addslashes(string $string): string
Backslash-escapes `'`, `"`, and `\`, and maps a NUL byte to the two-character sequence `\0`.
echo addslashes("O'Reilly"); // => O\'Reilly
# stripslashes
stripslashes(string $string): string
Removes one level of backslashes, keeping the escaped character — except `\0`, which becomes a real NUL. A trailing lone backslash is dropped.
echo stripslashes("O\\'Reilly"); // => O'Reilly
# str_rot13
str_rot13(string $string): string
Rotates ASCII letters by 13 places. Every other character, including all non-ASCII, passes through untouched. Applying it twice restores the input.
echo str_rot13("abc"); // => nop
# similar_text
similar_text(string $string1, string $string2): int
The number of matching characters, using PHP's own recursive longest-common-substring algorithm. DIVERGENCE: the by-reference `$percent` out-parameter does not exist, so only the count is available.
echo similar_text("World", "word"); // => 3
# levenshtein
levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1): int
Weighted edit distance over BYTES, so a multibyte character counts as several edits. Note the cost defaults apply only when the argument is absent — passing an explicit null coerces that cost to 0.
echo levenshtein("kitten", "sitting"); // => 3
# vsprintf
vsprintf(string $format, array $values): string
`sprintf` with the arguments supplied as one array, spread in iteration order with keys discarded.
echo vsprintf("%d-%s", [1, "x"]); // => 1-x
# vprintf
vprintf(string $format, array $values): int
Writes a `vsprintf` result to output and returns its length in BYTES.
vprintf("%d", [7]); // => 7
# sscanf
sscanf(string $string, string $format, mixed &...$vars): array|int|null
Parses the string against the format. With two arguments the result is an array of the converted values, padded with nulls to one entry per non-suppressed specifier; with by-reference arguments it is the number of specifiers processed, and a variable no conversion reached is left untouched. Supports `%d %D %i %o %x %X %u %f %e %E %g %s %c %n %[…] %%`, `*` suppression, field widths, the ignored `l`/`L`/`h` size modifiers, literal text and whitespace runs. When the input runs out before any conversion the answer is `null` (two-argument) or `-1` (by-reference). DIVERGENCE: the `%n$` positional form is not implemented, and no format is rejected up front the way PHP's `ValidateFormat` does.
print_r(sscanf("age:42", "age:%d")); // => Array\n(\n [0] => 42\n)
# htmlspecialchars_decode
htmlspecialchars_decode(string $string): string
Decodes only what `htmlspecialchars` can produce — `<`, `>`, `&`, `"` and the numeric single quote — and `$flags` gates the two quote entities. A named entity like `é` and any other numeric reference are left standing, which is what separates it from `html_entity_decode`.
echo htmlspecialchars_decode("a<b"); // => a<b
# html_entity_decode
html_entity_decode(string $string): string
Decodes the full HTML 4.01 named-entity table plus decimal (`é`) and hexadecimal (`é`) references; an unknown entity, and an `&` with no terminating `;`, are copied through verbatim. `$flags` gates the quote entities. DIVERGENCE: `$encoding` is not read (input is treated as UTF-8).
echo html_entity_decode("café"); // => café
# strip_tags
strip_tags(string $string): string
A port of `php_strip_tags_ex`, so its five-state scanner — HTML tag, `<? … ?>`, `<! … >`, `<!-- … -->`, and the `<?xml` / `<!DOCTYPE` re-entries — matches the reference on quoted attributes containing `>`, nested `<`, and unterminated tags. `$allowed_tags` is honoured in both spellings, the string `"<b><i>"` and the array `["b", "i"]`; an allowed tag is re-emitted verbatim, attributes included.
echo strip_tags("<p>hi</p>"); // => hi
# mb_strlen
mb_strlen(string $string): int
The number of Unicode CODEPOINTS, the multibyte counterpart of `strlen`. The `$encoding` argument is accepted and ignored — phplang strings are always UTF-8.
echo mb_strlen("héllo"); // => 5
# mb_strtoupper
mb_strtoupper(string $string): string
Full Unicode uppercase, which can change the string's length (German sharp s becomes `SS`). The `$encoding` argument is ignored.
echo mb_strtoupper("héllo"); // => HÉLLO
# mb_strtolower
mb_strtolower(string $string): string
Full Unicode lowercase. The `$encoding` argument is ignored.
echo mb_strtolower("HÉLLO"); // => héllo
# mb_substr
mb_substr(string $string, int $start, ?int $length = null): string
Codepoint-aware substring. A negative start counts from the end, a negative length omits that many trailing codepoints, and the end is clamped. The `$encoding` argument is ignored.
echo mb_substr("héllo", 1, 3); // => éll
Multibyte strings
# mb_str_split
mb_str_split(string $string, int $length = 1): array
Splits into chunks of `$length` CODEPOINTS; the final chunk may be shorter. A length below 1 throws `ValueError: mb_str_split(): Argument #2 ($length) must be greater than 0`.
echo mb_str_split("héllo")[1]; // => é
# mb_convert_case
mb_convert_case(string $string, int $mode): string
Mode 1 lowercases, mode 2 title-cases, and mode 0 — plus every UNRECOGNIZED mode — uppercases. Title-casing uppercases the first letter of each WORD and lowercases the rest of it; a Unicode case-ignorable character (an apostrophe, `.`, `:`) is transparent rather than a word break, so `"who's who"` becomes `"Who's Who"` while `"x,y"` becomes `"X,Y"`. DIVERGENCE: PHP's `MB_CASE_*_SIMPLE` modes (3-5) are not distinguished.
echo mb_convert_case("hello world", MB_CASE_TITLE); // => Hello World
# mb_strpos
mb_strpos(string $haystack, string $needle, int $offset = 0): int|false
The CODEPOINT index of the first match at or after the offset, or `false`. An offset outside `[-len, len]` raises `mb_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)`.
echo mb_strpos("héllo", "llo"); // => 2
# mb_stripos
mb_stripos(string $haystack, string $needle, int $offset = 0): int|false
Case-insensitive `mb_strpos`. DIVERGENCE: folding keeps only the first codepoint of each character's lowercase expansion so that indexes stay 1:1, which differs from PHP's full case mapping for characters like U+0130.
echo mb_stripos("HÉLLO", "é"); // => 1
# mb_strrpos
mb_strrpos(string $haystack, string $needle, int $offset = 0): int|false
The codepoint index of the LAST match, with the same offset rules and out-of-range error as `mb_strpos`. An empty needle returns the codepoint length.
echo mb_strrpos("héllo", "l"); // => 3
# mb_strripos
mb_strripos(string $haystack, string $needle, int $offset = 0): int|false
Case-insensitive `mb_strrpos`, using the same single-codepoint folding as `mb_stripos`.
# mb_substr_count
mb_substr_count(string $haystack, string $needle): int
Non-overlapping match count in codepoints. An empty needle throws `ValueError: mb_substr_count(): Argument #2 ($needle) must not be empty`.
echo mb_substr_count("ababa", "aba"); // => 1
# mb_str_pad
mb_str_pad(string $string, int $length, string $pad_string = " ", int $pad_type = STR_PAD_RIGHT): string
Pads to `$length` CODEPOINTS by cycling the pad string. All three `STR_PAD_*` modes are honoured, with `STR_PAD_BOTH` flooring the left side. An empty pad string throws `ValueError: mb_str_pad(): Argument #3 ($pad_string) must not be empty`, and only when padding is actually needed.
echo mb_str_pad("é", 3, "-"); // => é--
# mb_ord
mb_ord(string $string): int|false
The Unicode codepoint of the first character, or `false` for an empty string — the multibyte counterpart of `ord`, which reports a byte instead.
echo mb_ord("é"); // => 233
# mb_chr
mb_chr(int $codepoint): string|false
The character for a codepoint, or `false` when it is negative, a surrogate, or above U+10FFFF.
echo mb_chr(233); // => é
# mb_lcfirst
mb_lcfirst(string $string): string
Lowercases the first character with full Unicode folding and leaves the remainder untouched; the empty string maps to itself.
echo mb_lcfirst("École"); // => école
# mb_ucfirst
mb_ucfirst(string $string): string
Uppercases the first character with full Unicode folding, which may expand it to several codepoints.
echo mb_ucfirst("école"); // => École
# mb_scrub
mb_scrub(string $string): string
Returns the string verbatim. It is a genuine no-op here: a phplang string is always valid UTF-8, so there is never an ill-formed sequence to substitute.
# mb_strcut
mb_strcut(string $string, int $start, ?int $length = null): string
A BYTE-offset substring that never splits a character: the offsets count bytes, then both endpoints are floored to the nearest UTF-8 boundary.
echo mb_strcut("héllo", 0, 2); // => h
# mb_split
mb_split(string $pattern, string $string, int $limit = -1): array|false
Splits on a regular expression. A positive limit caps the piece count with the last piece holding the remainder. DIVERGENCE: the Rust `regex` crate replaces Oniguruma, so backreferences and lookaround are unsupported and an uncompilable pattern returns `false`.
print_r(mb_split("[,;]", "a,b;c")); // => Array\n(\n [0] => a\n [1] => b\n [2] => c\n)
# mb_convert_kana
mb_convert_kana(string $string, string $mode = "KV"): string
Converts between fullwidth and halfwidth ASCII by codepoint arithmetic: `a`/`r`/`n` fold fullwidth to halfwidth for printables, letters, and digits, `A`/`R`/`N` do the reverse, and `s`/`S` map the ideographic space. DIVERGENCE: the kana modes `k K h H c C v V` are unimplemented, which includes the DEFAULT mode `"KV"` — calling it with no mode is a no-op.
# mb_strwidth
mb_strwidth(string $string): int
The display width, counting 2 for codepoints in the 23 East-Asian wide and fullwidth ranges ported from PHP's own table and 1 for everything else. DIVERGENCE: combining marks and zero-width characters count as 1, not 0.
echo mb_strwidth("ab"); // => 2
# mb_convert_encoding
mb_convert_encoding(string $string, string $to_encoding): string
Maps unrepresentable characters to `?`: everything at or above U+0080 for `ASCII`, above U+00FF for `ISO-8859-1`/`Latin-1`. `UTF-8` returns the string unchanged; any OTHER target raises `ValueError: mb_convert_encoding(): Argument #2 ($to_encoding) must be a valid encoding, "…" given`. DIVERGENCE: `$from_encoding` is ignored, true single-byte output is impossible in a UTF-8 runtime, and the array form is unsupported.
echo mb_convert_encoding("héllo", "ASCII"); // => h?llo
# mb_detect_encoding
mb_detect_encoding(string $string, array|string|null $encodings = null): string|false
The first candidate the string satisfies, where `ASCII` requires every byte below 0x80 and `UTF-8` always succeeds; any other candidate never matches and an exhausted list yields `false`. The default order is ASCII then UTF-8. DIVERGENCE: `$strict` is ignored, and real detection is impossible because strings are always valid UTF-8.
echo mb_detect_encoding("abc"); // => ASCII
# mb_check_encoding
mb_check_encoding(string $value = "", ?string $encoding = null): bool
Returns true unless the encoding normalizes to `ASCII`, in which case it checks that every byte is below 0x80. DIVERGENCE: a missing `$value` checks the empty string rather than all previous output, and the array form is unsupported.
var_dump(mb_check_encoding("héllo", "ASCII")); // => bool(false)
# mb_internal_encoding
mb_internal_encoding(?string $encoding = null): string|bool
With no argument returns the stored encoding, defaulting to `UTF-8`; with one it stores the raw string and returns true. DIVERGENCE: the name is never validated, the stored value only affects `mb_check_encoding`, and the state is thread-local and NOT reset between evaluations on the same thread.
echo mb_internal_encoding(); // => UTF-8
Arrays
# array_column
array_column(array $rows, ?string $column_key, ?string $index_key = null): array
Plucks one column out of a list of rows, optionally re-keying by another column. A null column key keeps the whole row, and a row missing the column is skipped. Rows may be arrays or objects.
echo implode(",", array_column([["id" => 1], ["id" => 2]], "id")); // => 1,2
# array_chunk
array_chunk(array $array, int $size, bool $preserve_keys = false): array
Splits into a list of sub-arrays of at most `$size` elements. A size below 1 throws `ValueError: array_chunk(): Argument #2 ($length) must be greater than 0`.
echo count(array_chunk([1, 2, 3], 2)); // => 2
# array_fill_keys
array_fill_keys(array $keys, mixed $value): array
An array using each element of `$keys` as a key, every entry set to the same value.
print_r(array_fill_keys(["a"], 0)); // => Array\n(\n [a] => 0\n)
# array_pad
array_pad(array $array, int $size, mixed $value): array
Pads to `abs($size)` elements — a positive size pads on the right, a negative one on the left. Integer keys are renumbered and string keys preserved, as `array_merge` does.
echo implode(",", array_pad([1], 3, 0)); // => 1,0,0
# array_key_first
array_key_first(array $array): mixed
The first key in insertion order, or null for an empty array.
var_dump(array_key_first(["a" => 1, "b" => 2])); // => string(1) "a"
# array_key_last
array_key_last(array $array): mixed
The last key in insertion order, or null for an empty array.
var_dump(array_key_last(["a" => 1, "b" => 2])); // => string(1) "b"
# array_is_list
array_is_list(array $array): bool
True when the keys are exactly `0, 1, …, n-1` in order. A non-array argument returns false rather than raising a `TypeError`.
var_dump(array_is_list([1, 2]), array_is_list([1 => 1])); // => bool(true) bool(false)
# array_diff_key
array_diff_key(array $array, array ...$excludes): array
The entries of the first array whose KEY is absent from every other array. Keys are compared by their string form.
print_r(array_diff_key(["a" => 1, "b" => 2], ["a" => 9])); // => Array\n(\n [b] => 2\n)
# array_intersect_key
array_intersect_key(array $array, array ...$others): array
The entries of the first array whose key is present in every other array. It shares the `array_diff_key` arm with the test inverted.
print_r(array_intersect_key(["a" => 1, "b" => 2], ["a" => 9])); // => Array\n(\n [a] => 1\n)
# array_diff_assoc
array_diff_assoc(array $array, array ...$excludes): array
Like `array_diff_key` but matching on key AND value, both compared by string form.
# array_intersect_assoc
array_intersect_assoc(array $array, array ...$others): array
Keeps the entries whose key-and-value pair appears in every other array, comparing both by string form.
# array_merge_recursive
array_merge_recursive(array ...$arrays): array
Merges arrays, recursing into shared STRING keys: two arrays under one key merge, and anything else under a duplicate string key is gathered into a flat list. Integer keys always append. Nested arrays are deep-copied, so the inputs are never mutated.
print_r(array_merge_recursive(["k" => "a"], ["k" => "b"]));
// => Array\n(\n [k] => Array\n (\n [0] => a\n [1] => b\n )\n\n)
# array_replace
array_replace(array $base, array ...$rest): array
Starts from `$base`, then overwrites or inserts every entry of each later array by key. There is no recursion into nested arrays.
print_r(array_replace(["a" => 1], ["a" => 2])); // => Array\n(\n [a] => 2\n)
# array_replace_recursive
array_replace_recursive(array $base, array ...$rest): array
As `array_replace`, except that when both sides hold an array under the same key the two are merged recursively instead of the later one replacing the earlier wholesale. Any other type pairing, or a key present on only one side, replaces.
print_r(array_replace_recursive(["a" => ["b" => 1, "c" => 2]], ["a" => ["b" => 9]])); // => Array\n(\n [a] => Array\n (\n [b] => 9\n [c] => 2\n )\n\n)
# array_count_values
array_count_values(array $array): array
Tallies how often each value occurs. Only integer and string values are counted; every other value type is skipped, matching PHP.
print_r(array_count_values(["a", "a", "b"])); // => Array\n(\n [a] => 2\n [b] => 1\n)
# usort
usort(array $array, callable $callback): bool
Sorts by value with a user comparator, then reindexes from 0. The sort is stable, as PHP's is, and tolerates a comparator that contradicts itself (`fn() => random_int(-1, 1)`) by answering with some permutation rather than failing. The comparator runs the VM, so an exception it throws stops the sort.
$a = [3, 1, 2]; usort($a, fn($x, $y) => $x <=> $y); echo implode(",", $a); // => 1,2,3
# uasort
uasort(array $array, callable $callback): bool
Sorts by value with a user comparator while preserving the key association.
$a = ["b" => 2, "a" => 1]; uasort($a, fn($x, $y) => $x <=> $y);
echo implode(",", array_keys($a)); // => a,b
# uksort
uksort(array $array, callable $callback): bool
Sorts by KEY with a user comparator while preserving the key association.
$a = ["b" => 1, "a" => 2]; uksort($a, fn($x, $y) => strcmp($x, $y));
echo implode(",", array_keys($a)); // => a,b
# natsort
natsort(array $array): bool
Natural-order sort by value, preserving keys. Digit runs compare numerically, so `img12` sorts after `img2`. A run with a leading zero compares left-aligned, matching PHP's `strnatcmp`.
$a = ["img12", "img2"]; natsort($a); echo implode(",", $a); // => img2,img12
# natcasesort
natcasesort(array $array): bool
`natsort` with case folded before comparison.
# shuffle
shuffle(array $array): bool
Randomizes the order in place with Fisher-Yates and reindexes from 0. DIVERGENCE: the generator is a thread-local xorshift64 seeded lazily from the wall clock, not PHP's Mt19937, so `mt_srand()` does not make the order reproducible.
# array_rand
array_rand(array $array, int $num = 1): mixed
One random key, or an array of `$num` distinct keys in their original order. An EMPTY array raises `ValueError: array_rand(): Argument #1 ($array) must not be empty` — there is no `$num` that would work — while a `$num` outside `1..count($array)` on a non-empty array names argument #2 instead. Uses the same non-reproducible xorshift64 generator as `shuffle`.
# array_walk
array_walk(array $array, callable $callback, mixed $extra = null): bool
Calls `$callback($value, $key[, $extra])` for every element and returns true. The value is passed through a reference cell, so a `function (&$v)` callback writes back into the array, scalars included.
$a = [1, 2]; array_walk($a, function ($v, $k) { echo $k . $v; }); // => 0112
# compact
compact(string|array ...$names): array
Builds an array from the named variables, pulling each bound value out of the current scope. A name may itself be an array of names, recursively. A name that is not BOUND is skipped with `Warning: compact(): Undefined variable $x` — a variable holding null is bound, so it is captured with no warning — and an argument that is neither a string nor an array is skipped with `Warning: compact(): Argument #N must be string or array of strings, <type> given`, numbered by the top-level argument even when it is nested inside an array of names.
$x = 1; $y = 2; print_r(compact("x", "y")); // => Array\n(\n [x] => 1\n [y] => 2\n)
# extract
extract(array $array): int
Imports each string-keyed entry as a variable in the current scope and returns how many were set. Integer keys are skipped because they are not valid variable names. DIVERGENCE: the `$flags` and `$prefix` arguments do not exist.
extract(["n" => 7]); echo $n; // => 7
# end
end(array $array): mixed
Moves the array's internal pointer to the last element and returns its value; `false` for an empty array. The cursor lives in a side table keyed by the array handle with an append-invariant fingerprint, so appending does not rewind it — matching PHP.
$a = [1, 2, 3]; echo end($a); // => 3
# reset
reset(array $array): mixed
Moves the internal pointer to the first element and returns its value; `false` for an empty array.
$a = [1, 2, 3]; end($a); echo reset($a); // => 1
# current
current(array $array): mixed
The value at the internal pointer without moving it, or `false` once the pointer has fallen off either end.
$a = [1, 2]; echo current($a); // => 1
# pos
pos(array $array): mixed
The alias of `current`, sharing its match arm.
# key
key(array $array): mixed
The key at the internal pointer, or null once the pointer is off the end.
$a = ["a" => 1]; echo key($a); // => a
# next
next(array $array): mixed
Advances the internal pointer and returns the new element's value, or `false` once it runs past the end. The off-the-end state is sticky, as in PHP: further `next`/`prev` calls stay invalid until `reset` or `end`.
$a = [1, 2]; echo next($a); // => 2
# prev
prev(array $array): mixed
Steps the internal pointer back one element and returns its value, or `false` once it runs past the start, entering the same sticky invalid state.
$a = [1, 2]; end($a); echo prev($a); // => 1
Math
# expm1
expm1(float $num): float
Computes `exp($num) - 1` with the precision-preserving `exp_m1`, which matters for arguments near zero.
echo expm1(0); // => 0
# log1p
log1p(float $num): float
Computes `log(1 + $num)` precisely for arguments near zero.
echo log1p(0); // => 0
# sinh
sinh(float $num): float
Hyperbolic sine.
echo sinh(0); // => 0
# cosh
cosh(float $num): float
Hyperbolic cosine.
echo cosh(0); // => 1
# tanh
tanh(float $num): float
Hyperbolic tangent.
echo tanh(0); // => 0
# asinh
asinh(float $num): float
Inverse hyperbolic sine.
echo asinh(0); // => 0
# acosh
acosh(float $num): float
Inverse hyperbolic cosine. An argument below 1 yields NAN rather than raising.
echo acosh(1); // => 0
# atanh
atanh(float $num): float
Inverse hyperbolic tangent. An argument outside `(-1, 1)` yields NAN or an infinity rather than raising.
echo atanh(0); // => 0
# asin
asin(float $num): float
Arc sine in radians; `|$num| > 1` yields NAN.
echo asin(0); // => 0
# acos
acos(float $num): float
Arc cosine in radians; `|$num| > 1` yields NAN.
echo acos(1); // => 0
# atan
atan(float $num): float
Arc tangent in radians.
echo atan(0); // => 0
# atan2
atan2(float $y, float $x): float
The two-argument arc tangent of `$y / $x` in radians, using the signs of both operands to select the quadrant. Note the first argument is the ordinate.
echo atan2(0, 1); // => 0
# deg2rad
deg2rad(float $num): float
Converts degrees to radians as `$num * PI / 180`.
echo deg2rad(0); // => 0
# rad2deg
rad2deg(float $num): float
Converts radians to degrees as `$num * 180 / PI`.
echo rad2deg(0); // => 0
# hypot
hypot(float $x, float $y): float
The Euclidean length `sqrt($x**2 + $y**2)`, computed without intermediate overflow.
echo hypot(3, 4); // => 5
# fdiv
fdiv(float $num1, float $num2): float
Plain IEEE-754 division with no zero check, so a zero divisor yields `INF`, `-INF`, or `NAN` rather than aborting the way `/` does.
echo fdiv(1, 0); // => INF
# is_nan
is_nan(float $num): bool
True when the float cast of the argument is IEEE not-a-number.
var_dump(is_nan(NAN)); // => bool(true)
# is_finite
is_finite(float $num): bool
True when the value is neither infinite nor NAN.
var_dump(is_finite(1.0)); // => bool(true)
# is_infinite
is_infinite(float $num): bool
True for positive or negative infinity.
var_dump(is_infinite(INF)); // => bool(true)
# decbin
decbin(int $num): string
The binary rendering of an integer. DIVERGENCE: PHP treats the value as unsigned 64-bit, so a negative argument gives PHP's two's-complement bit string; here it gives a leading `-` followed by the magnitude.
echo decbin(5); // => 101
# decoct
decoct(int $num): string
The octal rendering of an integer, with the same negative-number divergence as `decbin`.
echo decoct(8); // => 10
# bindec
bindec(string $binary_string): int|float
Parses a base-2 string, dropping a leading `0b`/`0B` and skipping any character that is not a binary digit — skipping any raises `Deprecated: Invalid characters passed for attempted conversion, these have been ignored`. The result is an integer when it fits `PHP_INT_MAX` and a float otherwise; accumulation saturates rather than wrapping.
echo bindec("101"); // => 5
# octdec
octdec(string $octal_string): int|float
Parses a base-8 string, dropping a leading `0o`/`0O` and skipping invalid digits (which raises the same `Invalid characters passed for attempted conversion` deprecation as `bindec`), with the same integer-or-float result rule.
echo octdec("10"); // => 8
# base_convert
base_convert(string $num, int $from_base, int $to_base): string
Re-renders a number from one base to another with lowercase digits, silently dropping invalid digits. A base outside `2..36` is an error. Accumulation is 128-bit and saturating, so enormous values clamp instead of wrapping.
echo base_convert("ff", 16, 2); // => 11111111
# mt_getrandmax
mt_getrandmax(): int
Returns the constant 2147483647, the largest value `mt_rand()` produces with no arguments.
echo mt_getrandmax(); // => 2147483647
# getrandmax
getrandmax(): int
Returns the same constant 2147483647 for `rand()`.
echo getrandmax(); // => 2147483647
# srand
srand(?int $seed = null): void
Reseeds the shared generator, or reseeds from clock and counter entropy when called with no argument. DIVERGENCE: the generator is SplitMix64, not PHP's libc or Mt19937, so a given seed does not reproduce PHP's sequence — and `srand` and `mt_srand` share one generator.
# mt_srand
mt_srand(?int $seed = null, int $mode = MT_RAND_MT19937): void
Shares the `srand` arm and therefore the same SplitMix64 state. The `$mode` argument is accepted and ignored.
# rand
rand(int $min = 0, int $max = 2147483647): int
A value in the inclusive range. Inverted bounds are swapped rather than raising. DIVERGENCE: SplitMix64 rather than PHP's generator, and the modulo range mapping is slightly biased for spans that do not divide evenly into 2^64.
# mt_rand
mt_rand(int $min = 0, int $max = 2147483647): int
The same generator and range mapping as `rand`, except that `$min > $max` raises `Argument #2 ($max) must be greater than or equal to argument #1 ($min)`. It is NOT Mt19937.
# random_int
random_int(int $min, int $max): int
Draws fresh bits rather than using the seeded generator, and raises when `$min > $max`. DIVERGENCE: NOT cryptographically secure — the bits come from clock nanoseconds XOR an atomic counter, mixed with SplitMix64. Do not use it for keys, tokens, or salts.
Arbitrary precision — GMP
# gmp_init
gmp_init(string|int $num, int $base = 0): string
Parses the operand and returns its decimal representation as a plain STRING, not a GMP object. DIVERGENCE: `$base` is accepted and ignored — the parser auto-detects a `0x`/`0b` prefix and otherwise assumes decimal, yielding `0` for anything unparseable.
echo gmp_init("0xff"); // => 255
# gmp_add
gmp_add(string|int $num1, string|int $num2): string
True arbitrary-precision addition, returning the sum as a decimal string.
echo gmp_add("123456789012345678901234567890", "1"); // => 123456789012345678901234567891
# gmp_sub
gmp_sub(string|int $num1, string|int $num2): string
Arbitrary-precision subtraction as a decimal string.
echo gmp_sub("100", "1"); // => 99
# gmp_mul
gmp_mul(string|int $num1, string|int $num2): string
Arbitrary-precision multiplication as a decimal string.
echo gmp_mul("99999999999", "99999999999"); // => 9999999999800000000001
# gmp_div_q
gmp_div_q(string|int $num1, string|int $num2, int $rounding_mode = GMP_ROUND_ZERO): string
Integer quotient truncated toward zero; a zero divisor throws `DivisionByZeroError: gmp_div_q(): Argument #2 ($num2) Division by zero`. DIVERGENCE: `$rounding_mode` is accepted and ignored — only truncation is implemented.
echo gmp_div_q("10", "3"); // => 3
# gmp_div
gmp_div(string|int $num1, string|int $num2, int $rounding_mode = GMP_ROUND_ZERO): string
Shares the `gmp_div_q` arm, so its zero-divisor message still reads `gmp_div_q(): Division by zero` and `$rounding_mode` is ignored.
# gmp_div_r
gmp_div_r(string|int $num1, string|int $num2, int $rounding_mode = GMP_ROUND_ZERO): string
DIVERGENCE: it shares the `gmp_mod` arm, so it returns the SIGN-OF-DIVISOR remainder — for a positive modulus the result lies in `[0, |m|)` — not the truncating remainder PHP's `gmp_div_r` gives for a negative dividend.
# gmp_mod
gmp_mod(string|int $num1, string|int $num2): string
A remainder whose sign follows the DIVISOR: the raw remainder has `|m|` added when it is negative, so a positive modulus always yields a result in `[0, |m|)`. A zero modulus throws `DivisionByZeroError: gmp_mod(): Argument #2 ($num2) Modulo by zero`.
echo gmp_mod("-7", "3"); // => 2
# gmp_pow
gmp_pow(string|int $num, int $exponent): string
Arbitrary-precision exponentiation. DIVERGENCE: a negative exponent returns `"0"` instead of raising PHP's `ValueError`, and the exponent is narrowed to 32 bits, so values at or above 2^32 wrap silently.
echo gmp_pow("2", "64"); // => 18446744073709551616
# gmp_powm
gmp_powm(string|int $num, string|int $exponent, string|int $modulus): string
Modular exponentiation at true arbitrary precision. A zero modulus throws `DivisionByZeroError: Modulo by zero` and a negative exponent raises `Negative exponent not supported`.
echo gmp_powm("4", "13", "497"); // => 445
# gmp_gcd
gmp_gcd(string|int $num1, string|int $num2): string
The greatest common divisor by the Euclidean algorithm on absolute values, so the result is never negative.
echo gmp_gcd("12", "18"); // => 6
# gmp_lcm
gmp_lcm(string|int $num1, string|int $num2): string
The least common multiple, computed as `|a / gcd(a,b) * b|`. It returns `"0"` when either operand is zero.
echo gmp_lcm("4", "6"); // => 12
# gmp_abs
gmp_abs(string|int $num): string
The absolute value as a decimal string.
echo gmp_abs("-42"); // => 42
# gmp_neg
gmp_neg(string|int $num): string
Arithmetic negation as a decimal string.
echo gmp_neg("42"); // => -42
# gmp_and
gmp_and(string|int $num1, string|int $num2): string
Bitwise AND of the two big integers, using two's-complement semantics.
echo gmp_and("6", "3"); // => 2
# gmp_or
gmp_or(string|int $num1, string|int $num2): string
Bitwise OR of the two big integers.
echo gmp_or("6", "3"); // => 7
# gmp_xor
gmp_xor(string|int $num1, string|int $num2): string
Bitwise XOR of the two big integers.
echo gmp_xor("6", "3"); // => 5
# gmp_cmp
gmp_cmp(string|int $num1, string|int $num2): int
Compares the two big integers and returns `-1`, `0`, or `1` as a plain PHP int.
echo gmp_cmp("10", "9"); // => 1
# gmp_sign
gmp_sign(string|int $num): int
The sign of the value: `-1`, `0`, or `1`.
echo gmp_sign("-5"); // => -1
# gmp_sqrt
gmp_sqrt(string|int $num): string
The integer (floor) square root. There is no negative-input guard, so behaviour for a negative operand is whatever the underlying big-integer library does.
echo gmp_sqrt("17"); // => 4
# gmp_root
gmp_root(string|int $num, int $nth): string
The integer (floor) nth root. `$nth` is clamped to at least 1, and `$nth == 2` takes the dedicated square-root path while every other value uses a binary search. `$nth` is narrowed to 32 bits.
echo gmp_root("27", 3); // => 3
# gmp_fact
gmp_fact(int $num): string
The factorial, computed as a plain iterative big-integer product. A negative argument is clamped to 0, returning `"1"`, rather than raising. The running time is linear in `$num`.
echo gmp_fact(20); // => 2432902008176640000
# gmp_pow2
gmp_pow2(int $exponent): string
Two raised to the given power, with a negative exponent clamped to 0. DIVERGENCE: reference PHP's GMP extension has NO `gmp_pow2()` — this function exists only in phplang. The exponent is narrowed to 32 bits.
echo gmp_pow2(10); // => 1024
# gmp_strval
gmp_strval(string|int $num, int $base = 10): string
Renders the value in the requested base. DIVERGENCE: only bases 16, 2, and 8 are special-cased — every other base, including PHP-supported ones such as 36, silently falls through to decimal instead of raising.
echo gmp_strval("255", 16); // => ff
# gmp_intval
gmp_intval(string|int $num): int
Converts to a PHP int. DIVERGENCE: a value too large SATURATES to `PHP_INT_MAX` or `PHP_INT_MIN` rather than wrapping or truncating as PHP does.
echo gmp_intval("42"); // => 42
# gmp_prob_prime
gmp_prob_prime(string|int $num, int $repetitions = 10): int
Returns 0 for composite, 1 for probably prime, and 2 for definitely prime. It trial-divides by odd divisors up to `min(sqrt(n), 100000)` — reporting 2 when that fully covers the root — then runs Miller-Rabin with the FIXED bases 2, 3, 5, 7, 11, 13, 17. DIVERGENCE: `$repetitions` is accepted and ignored, so the error bound is not tunable.
echo gmp_prob_prime("97"); // => 2
# gmp_perfect_square
gmp_perfect_square(string|int $num): bool
True when the value is non-negative and its integer square root squared equals it.
var_dump(gmp_perfect_square("16")); // => bool(true)
Filesystem
# file_get_contents
file_get_contents(string $filename): string|false
Reads the whole file and returns it as a string, or `false` on any I/O error with no warning. DIVERGENCE: `$use_include_path`, `$context`, `$offset`, and `$length` are never read — a partial read is not possible — and the lossy UTF-8 conversion makes it binary-unsafe.
file_put_contents("/tmp/php_ref.txt", "hi"); echo file_get_contents("/tmp/php_ref.txt"); // => hi
# file_put_contents
file_put_contents(string $filename, mixed $data, int $flags = 0): int|false
Writes the data — an array is string-cast element-wise and concatenated with no separator — and returns the byte count. Only `FILE_APPEND` is honoured; `LOCK_EX` and `FILE_USE_INCLUDE_PATH` are accepted and ignored, so no locking happens.
echo file_put_contents("/tmp/php_ref.txt", "abc"); // => 3
# file
file(string $filename, int $flags = 0): array|false
Splits the file on `\n`, keeping each terminator by default. `FILE_IGNORE_NEW_LINES` and `FILE_SKIP_EMPTY_LINES` are honoured; `FILE_USE_INCLUDE_PATH` is ignored. A read failure returns `false`.
file_put_contents("/tmp/php_ref.txt", "a\nb");
echo count(file("/tmp/php_ref.txt", FILE_IGNORE_NEW_LINES)); // => 2
# readfile
readfile(string $filename): int|false
Writes the file's contents to output and returns the RAW byte count read from disk — which can exceed the length of what was emitted, since the text goes through the lossy conversion. `$use_include_path` and `$context` are not read.
file_put_contents("/tmp/php_ref.txt", "hi"); echo readfile("/tmp/php_ref.txt"); // => hi2
# file_exists
file_exists(string $filename): bool
Stats the path, following symlinks, so a broken symlink reports false. There is no stat cache to invalidate.
var_dump(file_exists("/")); // => bool(true)
# is_file
is_file(string $filename): bool
True when the path stats to a regular file, following symlinks. A failed stat is false.
var_dump(is_file("/")); // => bool(false)
# is_dir
is_dir(string $filename): bool
True when the path stats to a directory, following symlinks.
var_dump(is_dir("/")); // => bool(true)
# is_link
is_link(string $filename): bool
True when an `lstat` reports a symlink; the link is not followed.
var_dump(is_link("/")); // => bool(false)
# is_readable
is_readable(string $filename): bool
DIVERGENCE: it does NOT call `access(2)` with `R_OK` — it only checks that the path can be stat'd. A file that exists but cannot be opened by the current user still reports true.
var_dump(is_readable("/")); // => bool(true)
# is_writable
is_writable(string $filename): bool
DIVERGENCE: no `access(2)` call. It stats the path and reports the inverse of the read-only mode bit, so it ignores the effective uid and gid, ACLs, and read-only mounts. A missing path is false.
# is_writeable
is_writeable(string $filename): bool
The alias of `is_writable`, sharing its match arm and its mode-bits-only limitation.
# is_executable
is_executable(string $filename): bool
A REAL `access(2)` check with `X_OK` for the current user — the one predicate in this module that consults the effective credentials. A path containing an interior NUL, or a missing path, is false.
# filetype
filetype(string $filename): string|false
One of `dir`, `file`, `link`, `fifo`, `char`, `block`, `socket`, or `unknown`. It uses `lstat`, so a symlink reports `link` rather than its target's type. A failed stat returns `false`.
echo filetype("/"); // => dir
# filesize
filesize(string $filename): int|false
The file's size in bytes from a real stat, following symlinks. A failed stat returns `false`.
file_put_contents("/tmp/php_ref.txt", "abc"); echo filesize("/tmp/php_ref.txt"); // => 3
# filemtime
filemtime(string $filename): int|false
The modification time truncated to whole Unix seconds. Any failure returns `false` — including a pre-1970 mtime, which cannot be represented by the underlying duration.
# fileperms
fileperms(string $filename): int|false
The full `st_mode`, file-type bits included rather than just the permission bits — mask with `0777` to get what `chmod` would take. A failed stat returns `false`.
# stat
stat(string $filename): array|false
A real `stat(2)`, following symlinks. Returns the full 26-entry PHP array: the 13 fields `dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks` under numeric keys `0`-`12`, then the same values again under their names. No field is synthesized.
echo count(stat("/")); // => 26
# lstat
lstat(string $filename): array|false
The same 26-entry array as `stat`, built from an `lstat` so it describes the symlink itself rather than its target.
echo count(lstat("/")); // => 26
# clearstatcache
clearstatcache(bool $clear_realpath_cache = false, string $filename = ""): null
A no-op returning null. There is no stat cache in this implementation, so nothing is invalidated and neither argument is read.
var_dump(clearstatcache()); // => NULL
# unlink
unlink(string $filename): bool
Removes a file. Returns `false` on any error, including an attempt to remove a directory. `$context` is not read.
# mkdir
mkdir(string $directory, int $permissions = 0777, bool $recursive = false): bool
Creates a directory. `$recursive` is honoured and selects the create-all path. DIVERGENCE: `$permissions` is accepted and IGNORED — no mode is applied, so the directory gets the process umask default.
# rmdir
rmdir(string $directory): bool
Removes an empty directory. It is never recursive, so a non-empty directory returns `false`.
# rename
rename(string $from, string $to): bool
Renames a path with same-filesystem semantics. Errors return `false` silently.
# copy
copy(string $from, string $to): bool
Copies the source over the destination, creating or truncating it and carrying the permission bits. Symlinks are followed.
# touch
touch(string $filename, ?int $mtime = null): bool
Creates the file if absent — without truncating an existing one — and sets its modification time, defaulting to now. DIVERGENCE: PHP's third `$atime` argument is accepted positionally but never applied, because the underlying file handle exposes only the modification time.
# scandir
scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING): array|false
Lists a directory, SYNTHESIZING the `.` and `..` entries rather than reading them, then sorting by Rust byte order (not PHP's locale collation). Only the descending order is recognized; `SCANDIR_SORT_NONE` is not, so the result is always sorted. A read failure returns `false`.
var_dump(in_array(".", scandir("/tmp"))); // => bool(true)
# glob
glob(string $pattern, int $flags = 0): array
A hand-rolled matcher, NOT libc `glob(3)`. The pattern splits at its LAST `/`: everything before is a literal directory, so wildcards never expand across directory levels. Supported: `*`, `?`, and `[…]` with `a-z` ranges and `!`/`^` negation. NOT supported: `{a,b}` brace expansion and backslash escaping. `GLOB_ONLYDIR`, `GLOB_MARK`, and `GLOB_NOSORT` are honoured. Dotfiles are skipped unless the name pattern itself starts with `.`. DIVERGENCE: it never returns `false` — an unreadable directory yields an empty array.
file_put_contents("/tmp/php_ref_g.txt", ""); echo count(glob("/tmp/php_ref_g.*")); // => 1
# fnmatch
fnmatch(string $pattern, string $filename, int $flags = 0): bool
Runs the same hand-rolled matcher over the whole string. Only `FNM_CASEFOLD` is honoured, and its folding is ASCII-only. DIVERGENCE: `FNM_PATHNAME` is ignored so `*` and `?` cross `/`, `FNM_PERIOD` is ignored so `*` matches a leading dot, and backslash escaping does not exist.
var_dump(fnmatch("*.txt", "a.txt")); // => bool(true)
# basename
basename(string $path, string $suffix = ""): string
A pure string operation with no filesystem access: it strips all trailing `/`, then takes the text after the last remaining one. The suffix is removed only when doing so would not empty the result, so `basename("foo.txt", "foo.txt")` returns `foo.txt` unchanged. Windows `\` separators are not recognized.
echo basename("/a/b/c.txt", ".txt"); // => c
# dirname
dirname(string $path, int $levels = 1): string
Applies the one-level dirname `$levels` times. DIVERGENCE: a `$levels` of 0 or negative is clamped to 1 rather than raising `ValueError`. An all-slash path yields `/`, and a path with no `/` yields `.`.
echo dirname("/a/b/c.txt"), "|", dirname("/a/b/c.txt", 2); // => /a/b|/a
# pathinfo
pathinfo(string $path, int $flags = PATHINFO_ALL): array|string
Splits the basename at the LAST `.`, matching PHP's own `php_pathinfo`, so `.htaccess` gives extension `htaccess` and an empty filename. With no second argument it returns a map whose `extension` key is OMITTED ENTIRELY when the basename has no dot. With a selector it returns one string, or `""` when that piece is absent. No filesystem access.
echo pathinfo("/a/b.txt", PATHINFO_EXTENSION); // => txt
# realpath
realpath(string $path): string|false
Canonicalizes the path, resolving symlinks and `.`/`..` to an absolute path. The path must EXIST — a non-existent name under an existing parent returns `false`. Being the OS call, on macOS it returns the `/private`-prefixed form for `/tmp` and `/var`.
var_dump(realpath("/no/such/path")); // => bool(false)
# getcwd
getcwd(): string|false
The current working directory, or `false` when the syscall fails — for instance after the directory has been removed.
var_dump(strlen(getcwd()) > 0); // => bool(true)
# sys_get_temp_dir
sys_get_temp_dir(): string
The temporary directory, honouring `TMPDIR` and otherwise `/tmp`, with trailing slashes trimmed. It never returns `false`.
var_dump(strlen(sys_get_temp_dir()) > 0); // => bool(true)
# tempnam
tempnam(string $directory, string $prefix): string|false
Creates a new empty file and returns its path, falling back to the system temp directory when `$directory` is not one. DIVERGENCE: uniqueness comes from the process id plus a nanosecond timestamp rather than randomness, and although the file is created exclusively it is NOT chmod'ed to 0600 — it gets the default mode masked by the umask, so do not treat it as private.
# disk_free_space
disk_free_space(string $directory): float|false
A real `statvfs(2)`, reporting the space available to an UNPRIVILEGED process (the available-blocks count, not the free-blocks count). A failed call returns `false`.
var_dump(disk_free_space("/") > 0); // => bool(true)
# diskfreespace
diskfreespace(string $directory): float|false
The alias of `disk_free_space`, sharing its match arm.
# disk_total_space
disk_total_space(string $directory): float|false
A real `statvfs(2)` reporting the filesystem's total size in bytes.
var_dump(disk_total_space("/") > 0); // => bool(true)
File streams
# fopen
fopen(string $filename, string $mode): resource|false
Opens a buffered in-memory resource. A trailing `b` or `t` is a no-op. `r` loads the file and is read-only unless `+` is given; `w` and `x` are always writable and truncate or exclusively create on open; `c` preloads without truncating; `a` preloads and parks the cursor at the end but does NOT create the file until the first flush. An unreadable file, an existing path under `x`, or an unrecognized leading character returns `false`.
$f = fopen("/tmp/php_ref_s.txt", "w"); var_dump(is_resource($f)); fclose($f); // => bool(true)
# fread
fread(resource $stream, int $length): string|false
Copies up to `$length` bytes from the cursor and advances it. At end of file it returns `""`, not `false`; only a non-resource argument gives `false`. DIVERGENCE: a CLOSED resource still reads successfully, and the lossy UTF-8 conversion makes it binary-unsafe.
file_put_contents("/tmp/php_ref_s.txt", "abcdef");
$f = fopen("/tmp/php_ref_s.txt", "r"); echo fread($f, 3); fclose($f); // => abc
# fgets
fgets(resource $stream, ?int $length = null): string|false
Reads from the cursor through the next `\n` inclusive. A `$length` caps the read at one byte fewer, as in PHP. At end of file it returns `false`.
file_put_contents("/tmp/php_ref_s.txt", "a\nb");
$f = fopen("/tmp/php_ref_s.txt", "r"); echo trim(fgets($f)); fclose($f); // => a
# fwrite
fwrite(resource $stream, string $data, ?int $length = null): int|false
Overwrites or extends the buffer at the cursor, then IMMEDIATELY rewrites the whole file — so data reaches disk without waiting for `fclose`, at O(filesize) per call. `$length` truncates the data. Returns `false` when the resource is not writable, is closed, or is not a resource. Even in append mode the entire buffer is rewritten.
$f = fopen("/tmp/php_ref_s.txt", "w"); echo fwrite($f, "abc"); fclose($f); // => 3
# fputs
fputs(resource $stream, string $data, ?int $length = null): int|false
The alias of `fwrite`, sharing its match arm and its eager whole-file flush.
# fclose
fclose(resource $stream): bool
Flushes any dirty buffer and marks the resource closed. A second `fclose` returns `false`. The flush runs BEFORE the closed check, so a dirty already-closed resource is still written out.
$f = fopen("/tmp/php_ref_s.txt", "w"); var_dump(fclose($f), fclose($f)); // => bool(true) bool(false)
# fflush
fflush(resource $stream): bool
Rewrites the whole buffer to disk when it is dirty. DIVERGENCE: it returns true unconditionally — for a non-resource, a closed resource, and even a failed write.
# feof
feof(resource $stream): bool
True when the cursor has reached the end of the buffer, and true for anything that is not a resource. It does not consult the closed flag.
file_put_contents("/tmp/php_ref_s.txt", "a");
$f = fopen("/tmp/php_ref_s.txt", "r"); fread($f, 1); var_dump(feof($f)); fclose($f); // => bool(true)
# ftell
ftell(resource $stream): int|false
The current cursor position. It is the one accessor that DOES check the closed flag, returning `false` for a closed resource as well as a non-resource.
file_put_contents("/tmp/php_ref_s.txt", "abc");
$f = fopen("/tmp/php_ref_s.txt", "r"); fread($f, 2); echo ftell($f); fclose($f); // => 2
# fseek
fseek(resource $stream, int $offset, int $whence = SEEK_SET): int
Repositions the cursor; whence 1 is from the current position, 2 from the end, anything else from the start. DIVERGENCE: the result is CLAMPED to the buffer, so seeking past the end is impossible and PHP's seek-beyond-EOF-then-write zero fill cannot happen. Returns 0 on success and -1 only for a non-resource.
file_put_contents("/tmp/php_ref_s.txt", "abcdef");
$f = fopen("/tmp/php_ref_s.txt", "r"); fseek($f, 3); echo fread($f, 2); fclose($f); // => de
# rewind
rewind(resource $stream): bool
Seeks the cursor to 0. It does not flush, and it succeeds even for a closed resource; only a non-resource returns `false`.
file_put_contents("/tmp/php_ref_s.txt", "abc");
$f = fopen("/tmp/php_ref_s.txt", "r"); fread($f, 2); rewind($f); echo fread($f, 1); fclose($f); // => a
# stream_get_contents
stream_get_contents(resource $stream): string|false
Everything remaining from the cursor to the end of the buffer. DIVERGENCE: `$maxlength` and `$offset` are never read, so neither a length cap nor an offset seek is applied. A closed resource yields `""`; only a non-resource returns `false`.
file_put_contents("/tmp/php_ref_s.txt", "abc");
$f = fopen("/tmp/php_ref_s.txt", "r"); echo stream_get_contents($f); fclose($f); // => abc
# is_resource
is_resource(mixed $value): bool
True only for an OPEN stream resource — a closed handle reports false, as in PHP.
$f = fopen("/tmp/php_ref_s.txt", "w"); fclose($f); var_dump(is_resource($f)); // => bool(false)
# get_resource_type
get_resource_type(mixed $value): string|false
Always the literal `"stream"` for an open resource — there is only one resource kind in this runtime. A non-resource or closed resource returns `false`.
$f = fopen("/tmp/php_ref_s.txt", "w"); echo get_resource_type($f); fclose($f); // => stream
Misc
# array_find
array_find(array $array, callable $callback): mixed
The PHP 8.4 search: returns the first VALUE for which `$callback($value, $key)` is truthy — note the value comes first. No match returns null, which is indistinguishable from having matched a null element.
echo array_find([1, 8, 3], fn($v, $k) => $v > 5); // => 8
# array_find_key
array_find_key(array $array, callable $callback): mixed
The KEY of the first element for which `$callback($value, $key)` is truthy, or null when none matches. It shares `array_find`'s loop.
echo array_find_key([1, 8, 3], fn($v, $k) => $v > 5); // => 1
# array_any
array_any(array $array, callable $callback): bool
True as soon as `$callback($value, $key)` is truthy for some element; false for an empty array.
var_dump(array_any([1, 2], fn($v, $k) => $v > 1)); // => bool(true)
# array_all
array_all(array $array, callable $callback): bool
True when `$callback($value, $key)` is truthy for every element, and true for an empty array.
var_dump(array_all([2, 4], fn($v, $k) => $v % 2 === 0)); // => bool(true)
# array_udiff
array_udiff(array $array1, array $array2, ..., callable $value_compare_func): array
Keeps the entries of the first array whose VALUE compares non-zero against every value of every other array, preserving keys. The comparator is the LAST argument and any number of operand arrays may precede it. DIVERGENCE: fewer than three arguments silently returns an empty array instead of raising, and the comparison is the direct O(n·m) form rather than PHP's sort-then-compare.
echo implode(",", array_udiff([1, 2, 3], [2], fn($a, $b) => $a <=> $b)); // => 1,3
# array_uintersect
array_uintersect(array $array1, array $array2, ..., callable $value_compare_func): array
Keeps the entries of the first array whose value compares equal to some value in EVERY other array, preserving keys. Same comparator-last shape and same under-three-arguments behaviour as `array_udiff`.
echo implode(",", array_uintersect([1, 2, 3], [2, 3], fn($a, $b) => $a <=> $b)); // => 2,3
# array_diff_ukey
array_diff_ukey(array $array1, array $array2, ..., callable $key_compare_func): array
The key-comparing counterpart of `array_udiff`: an entry survives when its KEY compares non-zero against every key of every other array.
echo implode(",", array_diff_ukey(["a" => 1, "b" => 2], ["a" => 9], fn($x, $y) => strcmp($x, $y))); // => 2
# array_intersect_ukey
array_intersect_ukey(array $array1, array $array2, ..., callable $key_compare_func): array
Keeps the entries of the first array whose key compares equal to some key in every other array, using the caller's comparator.
echo implode(",", array_intersect_ukey(["a" => 1, "b" => 2], ["a" => 9], fn($x, $y) => strcmp($x, $y))); // => 1
# array_multisort
array_multisort(array $array1, mixed ...$rest): bool
Fully variadic: every array argument starts a new column and every scalar applies as a flag to the most recent column. Columns break ties left to right, and every column is reordered by the same permutation, reindexed from 0 with all keys discarded. Only `SORT_ASC`, `SORT_DESC`, `SORT_REGULAR`, `SORT_NUMERIC`, and `SORT_STRING` are recognized — `SORT_NATURAL`, `SORT_FLAG_CASE`, and any OR-combination are silently ignored. It returns `false` rather than raising when there is no array argument or the columns differ in length.
$a = [3, 1, 2]; $b = ["c", "a", "b"]; array_multisort($a, $b);
echo implode(",", $a), "|", implode(",", $b); // => 1,2,3|a,b,c
# array_walk_recursive
array_walk_recursive(array $array, callable $callback, mixed $arg = null): bool
Descends nested arrays and invokes `$callback($value, $key[, $arg])` on non-array LEAVES only — a nested array is recursed into but never passed to the callback. The leaf goes in through a reference cell, so a `function (&$v)` callback rewrites it in place, exactly as with `array_walk`.
$a = [1, [2, 3]]; array_walk_recursive($a, function ($v, $k) { echo $v; }); // => 123
# str_word_count
str_word_count(string $string, int $format = 0, ?string $characters = null): int|array
Format 1 returns the words, format 2 a byte-offset-to-word map, and 0 the count; any other value raises `ValueError: str_word_count(): Argument #2 ($format) must be a valid format value`. A word is a run of ASCII letters plus `'` and `-`, which are ALWAYS word characters; `$characters` only ADDS bytes to that set and can never restrict it. Matching is bytewise, so multibyte text is not recognized as words.
echo str_word_count("a b c"); // => 3
# strnatcmp
strnatcmp(string $string1, string $string2): int
A byte-for-byte port of PHP's natural-order comparison, returning only the sign. Leading zeros are stripped ONCE at the start of each string rather than per digit run, so `strnatcmp("1", "01")` is 0. A digit run compares left-aligned when either side starts with `0`, and by magnitude otherwise.
echo strnatcmp("img12", "img2"); // => 1
# strnatcasecmp
strnatcasecmp(string $string1, string $string2): int
`strnatcmp` with ASCII case folding, returning only the sign. Non-ASCII bytes are compared unfolded.
echo strnatcasecmp("IMG2", "img2"); // => 0
# soundex
soundex(string $string): string
The real Soundex algorithm, ported from PHP's own `soundex.c`: the first letter verbatim, then up to three consonant codes, right-padded with zeros. ASCII-only — a string with no ASCII letters returns `"0000"`.
echo soundex("Robert"); // => R163
# metaphone
metaphone(string $string, int $max_phonemes = 0): string
The real traditional Metaphone, ported from PHP's `metaphone.c` including the special first-phoneme cases and the CH, TH, DG, GH, and TIO/TIA rules — so `TH` encodes as the DIGIT zero and `CH` as `X`, as in PHP. ASCII-only; a letterless input returns the empty string. `$max_phonemes` caps the length, with 0 or negative meaning unlimited.
echo metaphone("Thompson"); // => 0MPSN
# str_getcsv
str_getcsv(string $string, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array
Parses ONE line. Doubled enclosures inside a quoted field are literals, and unenclosed fields keep their blanks. DIVERGENCE: the escape character keeps BOTH itself and the following character — it is not stripped; an embedded newline is an ordinary field character, so multi-line records are never reassembled; an empty input returns a one-element array holding null; and an empty `$separator` or `$enclosure` falls back to the DEFAULT rather than disabling it. Omitting `$escape` raises PHP 8.4's deprecation notice.
print_r(str_getcsv("a,b")); // => Array\n(\n [0] => a\n [1] => b\n)
# uniqid
uniqid(string $prefix = "", bool $more_entropy = false): string
A hex timestamp of the form `%08x%05x` from the wall clock's seconds and microseconds, prefixed by `$prefix`. `$more_entropy` is honoured and appends a fractional suffix of PHP's shape, but with only 1000 distinct values. DIVERGENCE: the clock is the ONLY entropy source — there is no RNG, no counter, and no collision avoidance, so two calls in the same microsecond return IDENTICAL strings. It is not suitable as a unique identifier under load.
var_dump(strlen(uniqid()) === 13); // => bool(true)
System and runtime environment
# getenv
getenv(?string $name = null, bool $local_only = false): string|array|false
Reads the REAL process environment. With no argument it returns every variable as an array; with a name, its value or `false`. The `$local_only` argument is accepted and ignored.
putenv("PHP_REF_X=1"); echo getenv("PHP_REF_X"); // => 1
# putenv
putenv(string $assignment): true
Mutates the REAL process environment, so the change is visible to `getenv` and to spawned children. `"K=V"` sets and a bare name unsets. It always returns true, never false.
putenv("PHP_REF_Y=2"); echo getenv("PHP_REF_Y"); // => 2
# getmypid
getmypid(): int
The real process id.
var_dump(getmypid() > 0); // => bool(true)
# getmyuid
getmyuid(): int
DIVERGENCE: a stub that always returns 0. The real user id is not queried.
echo getmyuid(); // => 0
# getmygid
getmygid(): int
DIVERGENCE: a stub that always returns 0, sharing the `getmyuid` arm.
echo getmygid(); // => 0
# phpversion
phpversion(?string $extension = null): string
DIVERGENCE: always the literal `"8.3.0"`. The `$extension` argument is never read, so a per-extension lookup returns the PHP version instead of `false`.
echo phpversion(); // => 8.3.0
# php_sapi_name
php_sapi_name(): string
DIVERGENCE: always `"cli"`, however the runtime was entered — REPL, language server, or embedded.
echo php_sapi_name(); // => cli
# php_uname
php_uname(string $mode = "a"): string
Built from the COMPILE-TIME target OS, the architecture, and the `HOSTNAME` environment variable (falling back to `localhost`) — not from a real `uname(2)`. Modes `s`, `n`, `m`, and `a` work; DIVERGENCE: `r` (release) and `v` (version) both return the EMPTY STRING.
var_dump(php_uname("r") === ""); // => bool(true)
# memory_get_usage
memory_get_usage(bool $real_usage = false): int
DIVERGENCE: a fixed 2097152 (2 MiB). phplang has no PHP-level allocator to report, so this is a stable conventional value rather than a measurement.
echo memory_get_usage(); // => 2097152
# memory_get_peak_usage
memory_get_peak_usage(bool $real_usage = false): int
Shares the `memory_get_usage` arm, so peak and current are ALWAYS the same fixed 2097152.
echo memory_get_peak_usage(); // => 2097152
# gc_collect_cycles
gc_collect_cycles(): int
DIVERGENCE: a no-op that collects nothing and always returns 0.
echo gc_collect_cycles(); // => 0
# gc_mem_caches
gc_mem_caches(): int
DIVERGENCE: a no-op that frees nothing and always returns 0.
echo gc_mem_caches(); // => 0
# gc_enable
gc_enable(): bool
DIVERGENCE: there is no collector state to toggle. It returns true, where PHP returns void.
# gc_disable
gc_disable(): bool
DIVERGENCE: a no-op returning true, where PHP returns void.
# gc_enabled
gc_enabled(): bool
DIVERGENCE: always true, unaffected by any preceding `gc_disable()`.
gc_disable(); var_dump(gc_enabled()); // => bool(true)
# extension_loaded
extension_loaded(string $extension): bool
Checks a HARDCODED allow-list rather than what the standard library actually implements: `core`, `standard`, `json`, `pcre`, `mbstring`, `ctype`, `filter`, `date`, `hash`, `spl`, `tokenizer`. Everything else — `gmp` and `bcmath` included, though both are implemented — reports false.
var_dump(extension_loaded("json"), extension_loaded("gmp")); // => bool(true) bool(false)
# sleep
sleep(int $seconds): int
DIVERGENCE: it does NOT sleep. The argument is never read and it returns 0 immediately, keeping the runtime responsive and tests deterministic.
echo sleep(3); // => 0
# usleep
usleep(int $microseconds): null
DIVERGENCE: it does NOT sleep, returning null immediately.
var_dump(usleep(1000)); // => NULL
# time_nanosleep
time_nanosleep(int $seconds, int $nanoseconds): null
DIVERGENCE: it does NOT sleep, and it shares the `usleep` arm — so it returns null where PHP returns true on success.
var_dump(time_nanosleep(0, 1)); // => NULL
# sys_getloadavg
sys_getloadavg(): array
DIVERGENCE: always the fixed list `[0.0, 0.0, 0.0]`. No load average is queried.
echo implode(",", sys_getloadavg()); // => 0,0,0
# get_defined_constants
get_defined_constants(bool $categorize = false): array
A real snapshot of the host's constant table as a flat name-to-value map, including anything `define()` has added. `$categorize` is never read, so the nested by-extension form is unavailable.
define("PHP_REF_K", 5); echo get_defined_constants()["PHP_REF_K"]; // => 5
# php_ini_loaded_file
php_ini_loaded_file(): false
DIVERGENCE: always false — no php.ini is modeled.
var_dump(php_ini_loaded_file()); // => bool(false)
# get_include_path
get_include_path(): false
DIVERGENCE: always false. PHP returns a string path list and reserves false for failure, so code doing string operations on the result will break.
var_dump(get_include_path()); // => bool(false)
# set_time_limit
set_time_limit(int $seconds): true
DIVERGENCE: accepted and ignored — no execution timer is installed. Always returns true.
var_dump(set_time_limit(30)); // => bool(true)
# ignore_user_abort
ignore_user_abort(?bool $enable = null): true
DIVERGENCE: accepted and ignored, storing no state, and returning true rather than PHP's previous-setting integer.
# error_reporting
error_reporting(?int $error_level = null): int
Read the error-reporting mask, or set it and return the PREVIOUS one. A diagnostic is displayed only when its `E_*` bit is set in the mask, so `error_reporting(0)` mutes every warning and notice. A missing argument and an explicit `null` both mean read-only. The mask starts at `E_ALL` unless `php -d error_reporting=…` seeded another. It cannot silence a COMPILE-time notice (`Using ${var} in strings …`), which was already decided when the file was read.
error_reporting(E_ALL & ~E_WARNING); echo $undefined; echo "quiet"; // => quiet
# ini_get
ini_get(string $option): string|false
The current value of an ini setting, always as a STRING, or false when the engine has no such setting. `error_reporting` reads back what was last written to it — the decimal mask after `error_reporting()` or `-d`, but the RAW string after an `ini_set`, which is why `ini_set("error_reporting", "12abc")` leaves `ini_get` reporting `"12abc"` while the mask is 12. The settings known here are PHP core plus `date` and `pcre` — the two extensions PHP 8 cannot be built without — carrying the values the reference reports for them with NO php.ini loaded, which is how each was established. DIVERGENCE: a name belonging to an optional extension the reference happened to be built with (`mysqli.default_host`), or one whose default is that build's install prefix (`extension_dir`, `include_path`), reports false rather than a value that would be wrong on another machine.
var_dump(ini_get("memory_limit"), ini_get("nosuch")); // => string(4) "128M" bool(false)
# ini_set
ini_set(string $option, mixed $value): string|false
Set an ini setting, returning its previous value as a string — or false, changing nothing, for a name the engine does not know (`ini_set` cannot invent settings) or one that is not runtime-changeable. That second group is PHP's `PHP_INI_PERDIR`/`PHP_INI_SYSTEM` set, which only a php.ini or `-d` may write while `ini_get` still reads it: `post_max_size`, `output_buffering`, `max_input_vars`, `expose_php`, `allow_url_fopen`, `arg_separator.input`, `disable_functions`, `hard_timeout`, `max_input_nesting_level`, `max_input_time`, `max_memory_limit`, `output_handler`, `register_argc_argv`, `zend.multibyte`, `zend.script_encoding`. Writing `error_reporting` also writes the mask, by ordinary string-to-int coercion and NOT the php.ini constant-expression scanner: `ini_set("error_reporting", "E_ALL & ~E_NOTICE")` reads as 0 and mutes everything, exactly as in the reference. The symbolic spelling works only on the `php -d error_reporting=…` path. DIVERGENCE: per-setting VALUE validation is not modelled — the reference refuses a value a setting will not take, with a warning and false, but `date.timezone` needs a zone database this build does not carry and `memory_limit`'s refusal quotes the process's live memory usage, which is not a reproducible number. Such a write is accepted here.
var_dump(ini_set("error_reporting", "0"), ini_get("error_reporting")); // => string(5) "30719" string(1) "0"
# spl_object_id
spl_object_id(object $object): int
The raw heap handle. It is STABLE for the process lifetime — the object table is push-only, so an id is never recycled after the value dies. DIVERGENCE: it is not object-only — arrays, closures, generators, and resources all get an id, and a non-heap value returns 0 rather than raising a `TypeError`.
$a = new stdClass; $b = new stdClass; var_dump(spl_object_id($a) !== spl_object_id($b)); // => bool(true)
# spl_object_hash
spl_object_hash(object $object): string
The same handle rendered as 32 zero-padded lowercase hex digits. DIVERGENCE: real PHP mixes in a per-process seed; this is trivially reversible to the id, so it must not be treated as opaque.
echo strlen(spl_object_hash(new stdClass)); // => 32
# func_get_args
func_get_args(): array
The arguments AS PASSED to the enclosing call, read from a hidden frame variable. Variadic arguments ARE included; parameters filled from their defaults are not, matching PHP. DIVERGENCE: named arguments are appended in CALL order rather than placed at their parameter positions, and at global scope it returns an empty array rather than raising.
function f() { return count(func_get_args()); } echo f(1, 2, 3); // => 3
# func_num_args
func_num_args(): int
How many arguments the enclosing call received. DIVERGENCE: outside a function it silently reports 0 instead of raising PHP's error.
function f() { return func_num_args(); } echo f(1, 2); // => 2
# func_get_arg
func_get_arg(int $position): mixed
One argument of the enclosing call by position. DIVERGENCE: an out-of-range or negative position returns null instead of raising `ArgumentCountError` or `ValueError`.
function f() { return func_get_arg(1); } echo f("a", "b"); // => b
# iterator_to_array
iterator_to_array(mixed $iterator, bool $preserve_keys = true): array
Materializes an array, an `IteratorAggregate`, an object following the `Iterator` protocol, or a plain object's public properties. DIVERGENCE: a GENERATOR is not accepted — it is a distinct handle kind the iteration path does not recognize, so `iterator_to_array($gen)` silently returns an EMPTY array. A scalar does the same.
echo count(iterator_to_array([1, 2, 3])); // => 3
# iterator_count
iterator_count(mixed $iterator): int
Counts what `iterator_to_array` would produce, and carries the same gap: a generator counts as 0.
echo iterator_count([1, 2]); // => 2
# iterator_apply
iterator_apply(mixed $iterator, callable $callback, ?array $args = null): int
DIVERGENCE: the subject is materialized ONLY to obtain a length, then the callback is invoked that many times with the elements of `$args` — it never receives the current element and the iterator is never advanced. Iteration stops early when the callback returns falsey. A generator yields a length of 0, so the callback never runs.
echo iterator_apply([1, 2, 3], fn() => true); // => 3
Reflection
# class_exists
class_exists(string $class, bool $autoload = true): bool
A case-insensitive lookup in the host class table, answering for CLASSES only: an interface, a trait or an enum shares that table but is reported by its own predicate (an enum, as in PHP, is both). The engine's own classes `Closure` and `Generator` answer too. `$autoload` is never read.
interface I {} var_dump(class_exists("I"), class_exists("Closure")); // => bool(false) bool(true)
# interface_exists
interface_exists(string $interface, bool $autoload = true): bool
True for a declared interface, and for the engine's own — `Traversable`, `Iterator`, `IteratorAggregate`, `Countable`, `ArrayAccess`, `Stringable`, `JsonSerializable`, `Throwable`, `UnitEnum`, `BackedEnum`. `$autoload` is never read.
interface I {} var_dump(interface_exists("I"), interface_exists("Traversable")); // => bool(true) bool(true)
# trait_exists
trait_exists(string $trait, bool $autoload = true): bool
True for a declared trait and false for every other kind of name. `$autoload` is never read.
trait T {} var_dump(trait_exists("T"), class_exists("T")); // => bool(true) bool(false)
# enum_exists
enum_exists(string $enum, bool $autoload = true): bool
True for a declared enum. An enum is the one name that is two kinds at once, so `class_exists()` answers for it as well — which is what PHP does.
enum E { case A; } var_dump(enum_exists("E"), class_exists("E")); // => bool(true) bool(true)
# get_class
get_class(object $object = null): string
The object's class name in its original DECLARED casing. A non-object is a `TypeError` — returning `false` was PHP 7's answer — and a boolean is named in that message by its VALUE (`true given`), not as `bool`. Called with NO argument it answers the enclosing `__CLASS__` and raises `Deprecated: Calling get_class() without arguments is deprecated`; outside a class that form is an `Error` instead.
class Foo {} echo get_class(new Foo); // => Foo
# get_parent_class
get_parent_class(object|string|null $object_or_class = null): string|false
The parent's name in declared casing, or `false` when there is none. It accepts an object, a class-name string, or nothing.
class A {} class B extends A {} echo get_parent_class(new B); // => A
# get_object_vars
get_object_vars(object $object): array
The properties of the instance the CALLING SCOPE may see, in insertion order — public ones from outside the class, everything from inside a method of it. A non-object is a `TypeError`, including an array.
class C { public $a = 1; } print_r(get_object_vars(new C)); // => Array\n(\n [a] => 1\n)
# get_class_methods
get_class_methods(object|string $object_or_class): array
The method names reachable through the class and its parents. DIVERGENCE: names come back LOWERCASED because the host stores them that way, private and protected methods are included, and an undeclared class yields an empty array rather than a `TypeError`.
class C { function Hello() {} } echo get_class_methods("C")[0]; // => hello
# get_class_vars
get_class_vars(string $class): array
DIVERGENCE: a stub — an EMPTY array for any declared class, and `false` for an undeclared one where the reference raises `TypeError: get_class_vars(): Argument #1 ($class) must be a valid class name, <name> given`. Property defaults are stored as compiled chunks with no evaluator reachable from this module, so no names or values are reported.
class C { public $a = 1; } print_r(get_class_vars("C")); // => Array\n(\n)
# get_declared_classes
get_declared_classes(): array
Every name in the class table — which always includes the 25 prelude classes, plus any user interfaces, traits, and enums. DIVERGENCE: names are LOWERCASED and the order is the hash map's, not declaration order.
var_dump(in_array("exception", get_declared_classes())); // => bool(true)
# class_parents
class_parents(object|string $object_or_class, bool $autoload = true): array|false
A nearest-first `name => name` map of the ancestor chain in declared casing, or `false` when the subject is neither an object nor a declared class.
class A {} class B extends A {} echo implode(",", class_parents(new B)); // => A
# class_implements
class_implements(object|string $object_or_class, bool $autoload = true): array|false
The interfaces the subject satisfies, transitively and through its parents, keyed by name. Engine ancestry answers too, so `class_implements("Generator")` reports `Iterator` and `Traversable`. An unknown name warns and returns `false`, as in PHP.
interface I {} class C implements I {} print_r(array_keys(class_implements(new C))); // => Array\n(\n [0] => I\n)
# class_uses
class_uses(object|string $object_or_class, bool $autoload = true): array|false
The traits THIS class composes with `use`, keyed by name — PHP does not walk the parent chain for it. An unknown name warns and returns `false`.
trait T {} class C { use T; } print_r(array_keys(class_uses("C"))); // => Array\n(\n [0] => T\n)
# method_exists
method_exists(object|string $object_or_class, string $method): bool
Resolves the method through the class and its parents, comparing the name case-insensitively as PHP does. The engine classes answer too (`method_exists($gen, "current")`). A subject that is neither an object nor a string is the `TypeError` PHP declares on the parameter.
class C { function m() {} } var_dump(method_exists("C", "M")); // => bool(true)
# property_exists
property_exists(object|string $object_or_class, string $property): bool
Checks the declared instance properties, walking parents, and — for an OBJECT subject — the live property list too. The name is compared case-SENSITIVELY. DIVERGENCE: static properties are never found, and a promoted constructor property is found on an instance but NOT on the class name, because promotion compiles to an assignment rather than a declaration.
class C { public $a; } var_dump(property_exists("C", "a")); // => bool(true)
# is_a
is_a(mixed $object_or_class, string $class, bool $allow_string = false): bool
True when the subject is, extends, or implements the named class, walking parents and interfaces transitively. `Throwable` is special-cased to match the `Exception` and `Error` roots even though no such class is declared. A string subject requires `$allow_string`.
class A {} class B extends A {} var_dump(is_a(new B, "A")); // => bool(true)
# is_subclass_of
is_subclass_of(mixed $object_or_class, string $class, bool $allow_string = true): bool
The same ancestry walk as `is_a`, but the subject's own class is excluded first, so only strict ancestors and interfaces count.
class A {} class B extends A {}
var_dump(is_subclass_of(new B, "A"), is_subclass_of(new A, "A")); // => bool(true) bool(false)