// PHPLANG — LANGUAGE REFERENCE

phplang v0.2.9 · PHP on fusevm · lex/parse → AST → fusevm bytecode → Cranelift JIT · no bespoke VM · the first compiled standalone PHP runtime · MIT · in active development

Docs GitHub
// Color scheme

>_LANGUAGE REFERENCE

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)

Cast

# (int)

(int) expr   //  also (integer)

Casts to integer by desugaring to a call to `intval`, which takes the leading numeric prefix of a string and yields `0` when there is none.

echo (int) "42px";   // => 42

# (float)

(float) expr   //  also (double), (real)

Casts to float by desugaring to `floatval`. All three spellings map to the same call.

echo (float) "3.5kg";   // => 3.5

# (string)

(string) expr

Casts to string by desugaring to `strval`. DIVERGENCE: an object stringifies to `Array` because `__toString` is not consulted.

echo (string) 123;   // => 123

# (bool)

(bool) expr   //  also (boolean)

Casts to boolean by desugaring to `boolval`. Falsey values are `0`, `0.0`, `""`, `"0"`, the empty array, and null.

var_dump((bool) 0, (bool) "0", (bool) "a");   // => bool(false) bool(false) bool(true)

# (array) (object) (unset)

(array) expr   //  NOT SUPPORTED

Listed for completeness: `cast_fn` in `src/parser.rs` maps only int, float, string, and bool. `(array)`, `(object)`, and `(unset)` are not recognized as casts, so `(array) $x` parses as a constant fetch of the bareword `array` followed by `$x` and is a syntax error.

Magic method

# __construct

public function __construct(mixed ...$args)

The constructor, invoked automatically by `new`. A parameter carrying a visibility or `readonly` keyword is promoted to a property of the same name and assigned before the body runs.

class P { function __construct(public $x) {} } echo (new P(4))->x;   // => 4

# __invoke

public function __invoke(mixed ...$args): mixed

Called when a value is used as a function. Declaring it makes every instance of the class callable — `$obj(…)`, `is_callable($obj)`, and passing `$obj` where a callback is expected (`array_map`, `usort`, `Closure::fromCallable`) all resolve through it.

class Twice { public function __invoke($x) { return $x * 2; } }
echo (new Twice)(21);   // => 42

# __toString

public function __toString(): string

Declared by the prelude `Exception` and `Error` classes and callable like any other method. DIVERGENCE: the runtime never invokes it implicitly — `echo $obj`, `(string) $obj`, and interpolation all produce `Array`, so it must be called explicitly.

class C { function __toString() { return "S"; } }
$o = new C; echo $o->__toString(), "|", $o;   // => S|Array

# __get

public function __get(string $name): mixed

Called when a property is read that the object does not carry — because no class declared it, because it was `unset`, or because its visibility puts it out of reach of the reading scope. Consulted BEFORE any access error, so a class with `__get` never reports `Cannot access private property`. While it runs for a given property it is not re-entered for that same property, so `__get($n) { return $this->$n; }` terminates rather than recursing.

class C { function __get($n) { return "g:$n"; } } echo (new C)->zz;   // => g:zz

# __set

public function __set(string $name, mixed $value): void

Called when a property is WRITTEN that the object does not carry or cannot reach. A write it handles creates no real property, so `get_object_vars` stays empty. In a read-modify-write (`$o->p .= "x"`, `$o->p++`) it is used only when the class ALSO defines `__get`: `__set` is half of a pair, and without `__get` supplying the old value the reference reads and writes the property directly instead.

class C { function __set($n, $v) { echo "set:$n"; } } $o = new C; $o->zz = 1;   // => set:zz

# __isset

public function __isset(string $name): bool

Called by `isset()` and `empty()` on a property the object does not carry or cannot reach. It answers the question by itself for `isset()`; for `empty()` and `??` a true answer is then followed by `__get` to obtain the value.

class C { function __isset($n) { return $n === "ok"; } }
$o = new C; var_dump(isset($o->ok), isset($o->no));   // => bool(true) bool(false)

# __unset

public function __unset(string $name): void

Called by `unset()` on a property the object does not carry or cannot reach. Like the others it is consulted before any access error, so a class defining it can be asked to unset a private property from outside without a fatal.

class C { function __unset($n) { echo "unset:$n"; } }
$o = new C; unset($o->zz);   // => unset:zz

# __rust_compile

__rust_compile(string $base64Body, int $line): void

The internal builtin a `rust { … }` block desugars to. It compiles and loads the encoded Rust body and registers each exported `pub extern "C"` function under its bare name. Programs call it only through the `rust` construct, never directly.

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: `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#039;`.

# 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 `&#039;`.

# 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 `&#039;` under `ENT_HTML401`). DIVERGENCE: `$encoding` and `$double_encode` are not read, so `&` is always re-encoded.

echo htmlspecialchars("a<b");   // => a&lt;b

# htmlentities

htmlentities(string $string): string

`htmlspecialchars` plus the full HTML 4.01 named-entity table, so `é` becomes `&eacute;`, `€` `&euro;`, and `α` `&alpha;`. `$flags` selects quote handling as it does there. DIVERGENCE: `$encoding` and `$double_encode` are not read.

echo htmlentities("café");   // => caf&eacute;

# 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 — math

# abs

abs(int|float $num): int|float

Absolute value, preserving the operand's numeric type.

echo abs(-5);   // => 5

# floor

floor(int|float $num): float

The largest integral value not greater than the operand, returned as a float.

echo floor(3.7);   // => 3

# ceil

ceil(int|float $num): float

The smallest integral value not less than the operand, returned as a float.

echo ceil(3.2);   // => 4

# round

round(int|float $num, int $precision = 0): float

Rounds to `$precision` decimal places, halves away from zero unless the PHP 8.4 `$mode` selects another rule; all four `PHP_ROUND_HALF_*` constants are honoured.

echo round(3.14159, 2);   // => 3.14

# sqrt

sqrt(int|float $num): float

The non-negative square root. A negative operand yields NAN.

echo sqrt(16);   // => 4

# pow

pow(int|float $base, int|float $exp): int|float

The base raised to the exponent. Two integers with a non-negative exponent give an integer result; anything else gives a float.

echo pow(2, 10);   // => 1024

# intdiv

intdiv(int $num1, int $num2): int

Integer division truncated toward zero. A zero divisor throws a catchable `DivisionByZeroError` with the message `Division by zero`.

echo intdiv(7, 2);   // => 3

# fmod

fmod(float $num1, float $num2): float

The floating-point remainder of the division, keeping the sign of the dividend.

echo fmod(7.5, 2);   // => 1.5

# sin

sin(float $num): float

Sine of an angle in radians.

echo sin(0);   // => 0

# cos

cos(float $num): float

Cosine of an angle in radians.

echo cos(0);   // => 1

# tan

tan(float $num): float

Tangent of an angle in radians.

echo tan(0);   // => 0

# exp

exp(float $num): float

e raised to the given power.

echo exp(0);   // => 1

# log

log(float $num, ?float $base = M_E): float

The natural logarithm, or the logarithm in `$base` when a second argument is supplied.

echo log(8, 2);   // => 3

# log10

log10(float $num): float

The base-10 logarithm.

echo log10(1000);   // => 3

# pi

pi(): float

Returns pi, the same value as the `M_PI` constant.

echo round(pi(), 2);   // => 3.14

# max

max(mixed ...$values): mixed
max(array $values): mixed

The largest argument, or the largest element when a single array is passed. Comparison follows PHP's loose ordering rules.

echo max(3, 1, 2);   // => 3

# min

min(mixed ...$values): mixed
min(array $values): mixed

The smallest argument, or the smallest element of a single array argument.

echo min(3, 1, 2);   // => 1

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 — `&lt;`, `&gt;`, `&amp;`, `&quot;` and the numeric single quote — and `$flags` gates the two quote entities. A named entity like `&eacute;` and any other numeric reference are left standing, which is what separates it from `html_entity_decode`.

echo htmlspecialchars_decode("a&lt;b");   // => a<b

# html_entity_decode

html_entity_decode(string $string): string

Decodes the full HTML 4.01 named-entity table plus decimal (`&#233;`) and hexadecimal (`&#xE9;`) 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&eacute;");   // => 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

Text formatting

# fprintf

fprintf(resource $stream, string $format, mixed ...$values): int|false

Formats with `sprintf`, writes the bytes to the stream, flushes a dirty file resource to disk, and returns the byte count — or `false` when the write fails.

$f = fopen("/tmp/x.txt", "w"); echo fprintf($f, "%d", 42); fclose($f);   // => 2

# vfprintf

vfprintf(resource $stream, string $format, array $values): int|false

`fprintf` with the format arguments supplied as one array, spread in iteration order with keys discarded.

# fscanf

fscanf(resource $stream, string $format): array|false

Reads one line from the stream and parses it with `sscanf`. DIVERGENCE: at end of file it returns `false`, not PHP's `-1`, deliberately so that `while ($r = fscanf(…))` terminates. The by-reference extra-argument form does not exist — unlike `sscanf`, which does implement it.

# array_change_key_case

array_change_key_case(array $array, int $case = CASE_LOWER): array

A copy with every STRING key ASCII-lowercased (case 0, the default and the fallback) or ASCII-uppercased (case 1). Integer keys pass through. Folding is ASCII-only and locale-independent, matching PHP, so key length never changes.

print_r(array_change_key_case(["AbC" => 1]));   // => Array\n(\n    [abc] => 1\n)

# get_html_translation_table

get_html_translation_table(int $table = HTML_SPECIALCHARS, int $flags = ENT_QUOTES): array

The character-to-entity map: `&`, `<`, and `>` always, plus `"` and `'` per the quote bits of `$flags`. With `$table` = 1 (`HTML_ENTITIES`) it appends the 96-entry ISO-8859-1 supplement U+00A0-U+00FF. DIVERGENCE: only the Latin-1 supplement is included, the `$encoding` argument is not read, and the doctype bits are ignored so `'` is always `&#039;`.

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 — bcmath

# bcadd

bcadd(string $num1, string $num2, ?int $scale = null): string

Exact arbitrary-precision decimal addition. The result is truncated toward zero to `$scale` fractional digits and rendered with exactly that many. `$scale` defaults to the `bcscale()` value and is clamped to `0..1048576`. An unparseable operand reads as `0` rather than raising PHP 8's `ValueError`.

echo bcadd("1.234", "5", 2);   // => 6.23

# bcsub

bcsub(string $num1, string $num2, ?int $scale = null): string

Exact decimal subtraction, with the same truncation, clamping, and lenient operand parsing as `bcadd`.

echo bcsub("5", "1.234", 2);   // => 3.76

# bcmul

bcmul(string $num1, string $num2, ?int $scale = null): string

Exact decimal multiplication, truncated toward zero to `$scale` fractional digits and padded to exactly that many.

echo bcmul("2", "3");   // => 6

# bcdiv

bcdiv(string $num1, string $num2, ?int $scale = null): string

Decimal division; a zero divisor throws `DivisionByZeroError: Division by zero`. DIVERGENCE: the quotient is computed at a 100-significant-digit context before truncation, so this is BOUNDED precision — a `$scale` needing more than about 100 significant digits is not reliable.

echo bcdiv("10", "3", 4);   // => 3.3333

# bcmod

bcmod(string $num1, string $num2, ?int $scale = null): string

The remainder `a - b * trunc(a / b)`, following PHP 7.2+ semantics. A zero divisor throws `DivisionByZeroError: Modulo by zero`. The intermediate division inherits the same bounded context as `bcdiv`.

echo bcmod("10", "3");   // => 1

# bcpow

bcpow(string $num, string $exponent, ?int $scale = null): string

Exponentiation by squaring on exact decimals. DIVERGENCE: a fractional exponent is silently truncated toward zero instead of raising PHP 8's `ValueError`, and an exponent beyond the 64-bit range saturates. A negative exponent yields the reciprocal, and zero to a negative power throws `DivisionByZeroError: Negative power of zero`.

echo bcpow("2", "10");   // => 1024

# bcsqrt

bcsqrt(string $num, ?int $scale = null): string

The square root truncated to `$scale` fractional digits. A negative operand throws `ValueError: bcsqrt(): Argument #1 ($num) must be greater than or equal to 0`. DIVERGENCE: computed at the same bounded 100-significant-digit context as `bcdiv`.

echo bcsqrt("2", 4);   // => 1.4142

# bccomp

bccomp(string $num1, string $num2, ?int $scale = null): int

Truncates both operands to `$scale` fractional digits and compares them, returning `-1`, `0`, or `1`. `$scale` defaults to the `bcscale()` value.

echo bccomp("1.001", "1", 2);   // => 0

# bcscale

bcscale(?int $scale = null): int

With no argument returns the current default scale; with one it sets the scale to `max(0, $scale)` and returns the PREVIOUS value. The scale is per-thread state, mirroring PHP's per-request scale.

bcscale(3); echo bcadd("1", "2");   // => 3.000

# bcpowmod

bcpowmod(string $num, string $exponent, string $modulus, ?int $scale = null): string

Modular exponentiation at TRUE arbitrary precision — all three operands are truncated to integers and a big-integer `modpow` does the work. A zero modulus throws `DivisionByZeroError: Modulo by zero`; a negative exponent throws `ValueError: bcpowmod(): Argument #2 ($exponent) must be greater than or equal to 0`. Because the result is an integer, `$scale` only controls how many trailing zero digits are appended.

echo bcpowmod("4", "13", "497");   // => 445

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)

Character classes

# ctype_alpha

ctype_alpha(mixed $text): bool

True when every byte is an ASCII letter.

var_dump(ctype_alpha("abc"), ctype_alpha("ab1"));   // => bool(true) bool(false)

# ctype_digit

ctype_digit(mixed $text): bool

True when every byte is an ASCII digit `0`-`9`.

var_dump(ctype_digit("123"));   // => bool(true)

# ctype_alnum

ctype_alnum(mixed $text): bool

True when every byte is an ASCII letter or digit.

var_dump(ctype_alnum("a1"));   // => bool(true)

# ctype_space

ctype_space(mixed $text): bool

True when every byte is space, tab, newline, vertical tab, form feed, or carriage return — matching C's `isspace`, deliberately including the vertical tab that Rust's own ASCII-whitespace test omits.

var_dump(ctype_space(" \t\n"));   // => bool(true)

# ctype_upper

ctype_upper(mixed $text): bool

True when every byte is an ASCII uppercase letter.

var_dump(ctype_upper("ABC"));   // => bool(true)

# ctype_lower

ctype_lower(mixed $text): bool

True when every byte is an ASCII lowercase letter.

var_dump(ctype_lower("abc"));   // => bool(true)

# ctype_punct

ctype_punct(mixed $text): bool

True when every byte is printable ASCII punctuation — graphic and not alphanumeric.

var_dump(ctype_punct("!?"));   // => bool(true)

# ctype_xdigit

ctype_xdigit(mixed $text): bool

True when every byte is a hexadecimal digit in either case.

var_dump(ctype_xdigit("1aF"));   // => bool(true)

# ctype_cntrl

ctype_cntrl(mixed $text): bool

True when every byte is an ASCII control character — 0x00-0x1F or 0x7F.

var_dump(ctype_cntrl("\n"));   // => bool(true)

# ctype_graph

ctype_graph(mixed $text): bool

True when every byte is printable and not a space — 0x21-0x7E.

var_dump(ctype_graph("a!"), ctype_graph("a "));   // => bool(true) bool(false)

# ctype_print

ctype_print(mixed $text): bool

True when every byte is printable, space included — 0x20-0x7E.

var_dump(ctype_print("a "));   // => bool(true)

Types and serialization

# is_scalar

is_scalar(mixed $value): bool

True only for an int, float, string, or bool. Null, arrays, objects, and closures are all false, matching PHP.

var_dump(is_scalar(1), is_scalar([]));   // => bool(true) bool(false)

# is_object

is_object(mixed $value): bool

True for a class instance, a closure or a generator; arrays are excluded. Closures and generators are `Closure` and `Generator` instances to the whole reflection surface, as in PHP. Arrays and objects share one handle representation here, so the distinction is asked of the host.

var_dump(is_object(new stdClass), is_object([]));   // => bool(true) bool(false)

# is_iterable

is_iterable(mixed $value): bool

True for an array, a generator, and an object whose class implements `Traversable`, `Iterator` or `IteratorAggregate`.

var_dump(is_iterable([1]), is_iterable(new stdClass));   // => bool(true) bool(false)

# is_countable

is_countable(mixed $value): bool

True for an array, or an object whose class implements `Countable`.

var_dump(is_countable([1]));   // => bool(true)

# get_debug_type

get_debug_type(mixed $value): string

The modern short type name: `null`, `bool`, `int`, `float`, `string`, `array`, `Closure`, or the class name. Use `gettype()` for PHP's legacy long names.

echo get_debug_type(1.5), "|", get_debug_type(null);   // => float|null

# serialize

serialize(mixed $value): string

Emits PHP's serialization format using `serialize_precision=-1` float rules: `a:` for arrays, `O:` for objects (with the engine's NUL-mangled visibility keys), `E:` for enum cases. DIVERGENCE: a structure that contains ITSELF is written as `N;` at the repeat, where the reference emits an `r:`/`R:` back-reference — the output stays finite and valid but no longer round-trips a cycle.

echo serialize([1, "a"]);   // => a:2:{i:0;i:1;i:1;s:1:"a";}

# unserialize

unserialize(string $data): mixed

Parses the `N b i d s a` tags and returns `false` on malformed input, trailing bytes, or a negative array count. DIVERGENCE: object (`O:`/`C:`/`E:`) and reference (`R:`/`r:`) records are unsupported, an oversized `i:` saturates rather than failing, and the `$options` argument is not read.

print_r(unserialize('a:1:{i:0;i:5;}'));   // => Array\n(\n    [0] => 5\n)

Regular expressions

# preg_match

preg_match(string $pattern, string $subject, array &$matches = [], int $flags = 0, int $offset = 0): int|false

Returns 1 on a match, 0 on none, or `false` for a pattern that will not compile — which also raises `Warning: preg_match(): <reason>` and leaves `preg_last_error()` at `PREG_INTERNAL_ERROR`. `$matches` is a real by-reference out-parameter and is written whether or not the caller initialised it. `PREG_OFFSET_CAPTURE` and `PREG_UNMATCHED_AS_NULL` are both honoured. `$offset` moves where the SEARCH starts without slicing the subject, so `^` still anchors to the real start and reported offsets are measured from it.

preg_match("/(\d+)/", "ab 42", $m); echo $m[1];   // => 42

# preg_match_all

preg_match_all(string $pattern, string $subject, array &$matches = [], int $flags = PREG_PATTERN_ORDER, int $offset = 0): int|false

Returns the number of matches, or `false` for a pattern that will not compile, with the same `Warning` and `preg_last_error()` state as `preg_match`. `$matches` is a real by-reference out-parameter, keyed by group name as well as index. `PREG_SET_ORDER` truncates each set at its own last participating group, so its rows are ragged where `PREG_PATTERN_ORDER`'s columns are full width. `PREG_OFFSET_CAPTURE` and `PREG_UNMATCHED_AS_NULL` are honoured alongside the order bit, and `$offset` moves where the search starts without slicing the subject.

$m = []; echo preg_match_all("/\d/", "a1b2", $m), $m[0][1];   // => 22

# preg_replace

preg_replace(string|array $pattern, string|array $replacement, string|array $subject, int $limit = -1, int &$count = null): string|array|null

Applies each pattern in turn. `$replacement` may be one string for every pattern or an array index-matched to the patterns (surplus patterns replace with `""`). The `$1`, `${1}`, and `\1` back-reference forms all work in the REPLACEMENT even though back-references in the pattern do not. The first pattern that will not compile ends the whole call at null, raising `Warning: preg_replace(): <reason>`; `&$count` receives the total number of replacements across every pattern, and is written even when that total is zero.

echo preg_replace("/\d+/", "#", "a1b22");   // => a#b#

# preg_replace_callback

preg_replace_callback(string|array $pattern, callable $callback, string|array $subject, int $limit = -1, int &$count = null, int $flags = 0): string|array|null

Calls the callback for each match with a fully POPULATED `$matches` array. `$limit` is honoured, and a pattern that will not compile returns null after raising `Warning: preg_replace_callback(): <reason>`. `&$count` receives the number of replacements, and `$flags` takes `PREG_OFFSET_CAPTURE`/`PREG_UNMATCHED_AS_NULL`, which reshape the `$matches` the callback receives.

echo preg_replace_callback("/\d/", fn($m) => $m[0] * 2, "a1b2");   // => a2b4

# preg_split

preg_split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array|false

Splits on each match. All three flag bits are honoured: `PREG_SPLIT_NO_EMPTY`, `PREG_SPLIT_DELIM_CAPTURE`, and `PREG_SPLIT_OFFSET_CAPTURE`. A zero-width match sitting right after a non-empty one is emitted, as PCRE does, so `/x*/` and `/\d*/` split the way the reference splits them. The captured delimiters `PREG_SPLIT_DELIM_CAPTURE` interleaves are positional — unlike `$matches`, they are not keyed by group name.

echo implode("-", preg_split("/[\s,]+/", "a, b  c"));   // => a-b-c

# preg_quote

preg_quote(string $str, ?string $delimiter = null): string

Backslash-escapes the PCRE special set `.\+*?[^]$(){}=!<>|:-#`, plus the first character of `$delimiter` when supplied. A NUL byte becomes the four-character sequence `\000`. It compiles nothing, so it never touches `preg_last_error()`.

echo preg_quote("1+1");   // => 1\+1

# preg_grep

preg_grep(string $pattern, array $array, int $flags = 0): array|false

The entries whose string value matches the pattern, PRESERVING keys. The `PREG_GREP_INVERT` bit inverts the test. A non-array subject yields an empty array rather than raising.

echo implode(",", preg_grep("/^a/", ["apple", "pear"]));   // => apple

# preg_last_error

preg_last_error(): int

The outcome of the last `preg_*` call that reached the regex compiler: `PREG_INTERNAL_ERROR` (1) after a pattern the engine rejected, `PREG_NO_ERROR` (0) after one it accepted. The state is STICKY and cleared only by another compile — reading it does not clear it, and `preg_quote()` does not touch it — so a pattern that compiles resets it even when the match then finds nothing. DIVERGENCE: only these two codes are produced; the reference also reports backtrack- and recursion-limit exhaustion, which this engine has no equivalent of.

@preg_match("/[a", "x"); echo preg_last_error();   // => 1

# preg_last_error_msg

preg_last_error_msg(): string

The message for the `preg_last_error()` code, and like it a pure reader that clears nothing. `"No error"` or `"Internal error"` in practice, the two codes this engine produces.

@preg_match("/[a", "x"); echo preg_last_error_msg();   // => Internal error

JSON

# json_decode

json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed

A hand-written recursive-descent parser over the raw bytes. A JSON array always decodes to a PHP array; a JSON object decodes to a `stdClass` unless `$associative` is truthy, or it is left null and `$flags` carries `JSON_OBJECT_AS_ARRAY`. Objects take their creation-order handles where `ext/json` allocates them — as each object's first member closes — so `json_decode('{"a":{"b":1}}')` numbers the inner `#1` and the outer `#2`. `$depth` is honoured and RANGE-CHECKED: below 1 raises `ValueError: json_decode(): Argument #3 ($depth) must be greater than 0` and past `INT_MAX` raises `… must be less than 2147483647`. `JSON_THROW_ON_ERROR` is honoured; other `$flags` are ignored. Integral numbers become ints; anything with a `.`/`e` or beyond the 64-bit range becomes a float. Any other failure returns null and records the code for `json_last_error()`. DIVERGENCE: the parser recurses on the native stack, so nesting deeper than 1024 reports `JSON_ERROR_DEPTH` whatever `$depth` asked for.

echo json_decode('{"a":1}')->a;   // => 1

# json_validate

json_validate(string $json, int $depth = 512, int $flags = 0): bool

Runs the same grammar without allocating any array and returns whether the input is well-formed, setting `json_last_error()` either way. `$depth` follows the same default-512, clamp-to-1 rule; `$flags` is ignored.

var_dump(json_validate('{"a":1}'), json_validate('{'));   // => bool(true) bool(false)

# json_last_error

json_last_error(): int

The `JSON_ERROR_*` code from the last decode or validation. DIVERGENCE: PHP keeps this per request; here it is thread-local state that lives for the thread's lifetime, and `json_encode()` never touches it.

json_decode("{"); echo json_last_error();   // => 4

# json_last_error_msg

json_last_error_msg(): string

The message matching `json_last_error()`, using PHP's own wording — `No error`, `Maximum stack depth exceeded`, `State mismatch (invalid or malformed JSON)`, `Control character error, possibly incorrectly encoded`, `Syntax error`, `Malformed UTF-8 characters, possibly incorrectly encoded`, or `Single unpaired UTF-16 surrogate in unicode escape`.

json_decode("{"); echo json_last_error_msg();   // => Syntax error

Encoding

# base64_encode

base64_encode(string $string): string

Standard padded base64 with the `+`/`/` alphabet, over the UTF-8 bytes of the argument.

echo base64_encode("abc");   // => YWJj

# base64_decode

base64_decode(string $string, bool $strict = false): string|false

A faithful port of PHP's own decoder, including its reverse table which skips tab, newline, carriage return, and space. `$strict` is honoured and rejects an invalid character, data after padding, a truncated final group, and wrong padding length. Non-strict mode silently skips unrecognized bytes.

echo base64_decode("YWJj");   // => abc

# hex2bin

hex2bin(string $string): string|false

Decodes a hex string to bytes, returning `false` on an odd length or a non-hex character. The counterpart `bin2hex` lives in the core library, not this module.

echo hex2bin("616263");   // => abc

# quoted_printable_encode

quoted_printable_encode(string $string): string

A port of PHP's encoder: soft-wraps at 75 columns with `=` and CRLF, and emits `=XX` in uppercase hex for control bytes, 0x7F, high bytes, `=` itself, and a space directly before a carriage return.

echo quoted_printable_encode("a=b");   // => a=3Db

# quoted_printable_decode

quoted_printable_decode(string $string): string

Decodes `=XX` sequences, removes soft line breaks, drops a trailing bare `=`, and passes any other malformed `=` through verbatim — matching PHP.

echo quoted_printable_decode("a=3Db");   // => a=b

# convert_uuencode

convert_uuencode(string $string): string

A port of PHP's uuencoder: 45-byte lines each prefixed by an encoded length character, three bytes to four printable characters, sextet zero mapped to a backtick, and a backtick-plus-newline trailer.

echo trim(convert_uudecode(convert_uuencode("abc")));   // => abc

# convert_uudecode

convert_uudecode(string $string): string|false

A port of PHP's uudecoder including its sanity checks: `false` for empty input, a declared line length larger than the buffer, an overrunning encoded span, or nothing decoded. A zero-length (backtick) line terminates.

# utf8_encode

utf8_encode(string $string): string

The deprecated Latin-1 to UTF-8 shim: each BYTE is widened to the codepoint of the same value. Exact for ASCII; already-multibyte input is widened byte by byte into mojibake, which is what real PHP does to the same byte sequence.

echo utf8_encode("abc");   // => abc

# utf8_decode

utf8_decode(string $string): string

The deprecated UTF-8 to Latin-1 shim: each codepoint at or below U+00FF is narrowed to one byte and anything above becomes `?`. DIVERGENCE: the byte string is then re-widened one Latin-1 character per byte, so a Latin-1-representable argument round-trips to ITSELF rather than shrinking — `utf8_decode("é")` is still `"é"` and `strlen()` of it is still 2. Only codepoints above U+00FF actually change.

echo utf8_decode("héllo"), "|", utf8_decode("€");   // => héllo|?

URL

# urlencode

urlencode(string $string): string

RFC 1738 percent-encoding over the string's bytes: letters, digits, `-`, `_`, and `.` pass through, a space becomes `+`, and everything else — the tilde included — becomes an uppercase `%XX`.

echo urlencode("a b&c");   // => a+b%26c

# urldecode

urldecode(string $string): string

Decodes `%XX` escapes and maps `+` back to a space. A `%` not followed by two hex digits is emitted verbatim, matching PHP. Note a `%XX` sitting at the very END of the string is not decoded.

echo urldecode("a+b%26c");   // => a b&c

# rawurlencode

rawurlencode(string $string): string

RFC 3986 percent-encoding: letters, digits, `-`, `_`, `.`, and `~` pass through (the tilde is preserved, unlike `urlencode`), and a space becomes `%20`.

echo rawurlencode("a b~c");   // => a%20b~c

# rawurldecode

rawurldecode(string $string): string

The `urldecode` decoder with plus-to-space turned off, so `+` stays literal. It shares the same end-of-string `%XX` behaviour.

echo rawurldecode("a%20b+c");   // => a b+c

# http_build_query

http_build_query(array $data, string $numeric_prefix = "", ?string $arg_separator = "&", int $encoding_type = PHP_QUERY_RFC1738): string|false

Serializes recursively to `k=v` pairs, url-encoding keys and values. All four arguments are honoured: `$numeric_prefix` is prepended to top-level integer keys, an explicit empty separator is respected, and `$encoding_type == 2` switches to `rawurlencode`. Nested arrays become `key[sub]` segments, null leaves are skipped, and booleans serialize as `1`/`0`. A non-array subject returns `false`.

echo http_build_query(["a" => 1, "b" => 2]);   // => a=1&b=2

# parse_url

parse_url(string $url, int $component = -1): array|string|int|null|false

A faithful port of PHP's own parser, so relative schemes, `mailto:`, bracketed IPv6 hosts, schemeless `host:port`, and the `file:///c:/` drive case all match reference PHP. Control bytes in each component are replaced with `_`. A non-negative `$component` selects one piece by its `PHP_URL_*` ordinal; ANY negative one asks for the whole array, and a positive one past `PHP_URL_FRAGMENT` raises `ValueError: parse_url(): Argument #2 ($component) must be a valid URL component identifier, N given` before the URL is even parsed. An out-of-range port, an empty host, or a bad authority returns `false`.

echo parse_url("https://example.com/p?q=1")["host"];   // => example.com

# parse_str

parse_str(string $string): array

Parses a query string into an array, handling `key[]` appends, `key[sub]` nesting, and PHP's mangling of interior `.` and space to `_` in top-level keys. DIVERGENCE: PHP writes into a by-reference second argument and returns void; phplang has no by-reference out-parameter, so this RETURNS the array instead — write `$r = parse_str($s);`.

$r = parse_str("a=1&b=2"); echo $r["a"], $r["b"];   // => 12

Hashing

# md5

md5(string $string, bool $binary = false): string

MD5 of the argument's UTF-8 bytes, as 32 lowercase hex characters. DIVERGENCE: `$binary` returns the digest mapped one Latin-1 character per byte, so `strlen()` of it is not 16 whenever a digest byte reaches 0x80.

echo md5("abc");   // => 900150983cd24fb0d6963f7d28e17f72

# sha1

sha1(string $string, bool $binary = false): string

SHA-1 as 40 lowercase hex characters, with the same raw-output caveat as `md5` when `$binary` is truthy.

echo sha1("abc");   // => a9993e364706816aba3e25717850c26c9cd0d89d

# crc32

crc32(string $string): int

The reflected CRC-32 (the zlib polynomial) as a PHP integer. No second argument is read.

echo crc32("abc");   // => 891568578

# hash

hash(string $algo, string $data, bool $binary = false): string

Digests the data with one of exactly six algorithms: `md5`, `sha1`, `sha256`, `sha512`, `crc32b`, `crc32`. DIVERGENCE: `sha384` is NOT accepted here even though `hash_hmac`, `hash_file`, and `hash_pbkdf2` accept it. An unknown name throws `ValueError: hash(): Argument #1 ($algo) must be a valid hashing algorithm`. PHP 8.1's `$options` array does not exist.

echo hash("sha256", "");   // => e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

# hash_hmac

hash_hmac(string $algo, string $data, string $key, bool $binary = false): string

RFC 2104 HMAC over the same digests, with the block size paired per algorithm. Exactly five names are accepted — `md5`, `sha1`, `sha256`, `sha384`, `sha512` — so the CRC variants `hash()` allows are rejected. An unknown name throws `ValueError: hash_hmac(): Argument #1 ($algo) must be a valid cryptographic hashing algorithm`. `$key` is `#[\SensitiveParameter]`, so a backtrace prints it as `Object(SensitiveParameterValue)` rather than the key itself.

echo strlen(hash_hmac("sha256", "m", "k"));   // => 64

# hash_algos

hash_algos(): array

The fixed six-element list `md5, sha1, sha256, sha512, crc32, crc32b`. DIVERGENCE: it is an accurate roster only for `hash()` — it omits `sha384`, which `hash_hmac`, `hash_file`, `hash_hmac_file`, and `hash_pbkdf2` all accept.

echo count(hash_algos());   // => 6

Hashing — files and key derivation

# md5_file

md5_file(string $filename, bool $binary = false): string|false

MD5 of the file's RAW bytes read straight off disk, so unlike the string functions this is byte-faithful for binary files. Any I/O error returns `false` with no warning.

# sha1_file

sha1_file(string $filename, bool $binary = false): string|false

SHA-1 of the file's raw bytes, returning `false` on any I/O error.

# hash_file

hash_file(string $algo, string $filename, bool $binary = false): string|false

Validates the algorithm BEFORE touching the filesystem, matching PHP. Six names are accepted: `md5`, `sha1`, `sha256`, `sha384`, `sha512`, `crc32b` — a set that is neither a subset nor a superset of what `hash()` takes, since it adds `sha384` and drops the non-reflected `crc32`. An unreadable file returns `false`.

# hash_hmac_file

hash_hmac_file(string $algo, string $filename, string $key, bool $binary = false): string|false

HMACs the file's raw bytes under the key. The same five names as `hash_hmac` are accepted, validated before the read; an unreadable file returns `false`.

# hash_equals

hash_equals(string $known_string, string $user_string): bool

A timing-safe comparison: it returns false immediately on a length mismatch (length is not treated as secret, as in PHP) and otherwise ORs the per-byte XOR across the whole length, so the running time does not reveal where the first difference is.

var_dump(hash_equals("abc", "abc"));   // => bool(true)

# hash_pbkdf2

hash_pbkdf2(string $algo, string $password, string $salt, int $iterations, int $length = 0, bool $binary = false): string

PBKDF2 per RFC 2898 over the local HMAC, accepting `md5`, `sha1`, `sha256`, `sha384`, `sha512`. `$length` is the length of the OUTPUT STRING — derived bytes for binary output, hex characters otherwise — and 0 emits the full digest. Throws a `ValueError` on an unknown algorithm, non-positive iterations, or a negative length. `$password` is `#[\SensitiveParameter]`, so a backtrace prints it as `Object(SensitiveParameterValue)`. DIVERGENCE: a length above 16777216 is refused (PHP instead attempts the allocation and dies on the memory limit).

echo strlen(hash_pbkdf2("sha256", "p", "s", 10, 20));   // => 20

# random_bytes

random_bytes(int $length): string

DIVERGENCE — NOT A CSPRNG. No OS-entropy dependency is available to this crate, so the bytes come from a SplitMix64 generator seeded from wall-clock nanoseconds XOR the process id. Use it for tests and filler only, never for keys, tokens, or salts. Output is the Latin-1 raw mapping, so `strlen()` exceeds `$length` whenever a generated byte reaches 0x80. A length below 1 throws `ValueError: random_bytes(): Argument #1 ($length) must be greater than 0`. DIVERGENCE: a length above 16 MiB is refused with a `ValueError` too (PHP instead attempts the allocation and dies on the memory limit).

Date and time

# time

time(): int

The current Unix timestamp in whole seconds. Any arguments passed are ignored.

var_dump(time() > 1700000000);   // => bool(true)

# mktime

mktime(int $hour = ?, int $minute = ?, int $second = ?, int $month = ?, int $day = ?, int $year = ?): int|false

Builds a timestamp in UTC ONLY, with each omitted field defaulting to the corresponding component of the current UTC time. Out-of-range fields normalize (month 13 rolls to next January, day 0 to the previous month's last day), and two-digit years map 0-69 to 2000-2069 and 70-100 to 1970-2000. DIVERGENCE: an overflow returns `false` rather than a far-future timestamp.

echo mktime(0, 0, 0, 1, 1, 1970);   // => 0

# gmmktime

gmmktime(int $hour = ?, int $minute = ?, int $second = ?, int $month = ?, int $day = ?, int $year = ?): int|false

Shares the `mktime` arm and is therefore identical to it, because every computation in this module is already UTC.

echo gmmktime(0, 0, 0, 1, 1, 1970);   // => 0

# date

date(string $format, int $timestamp = time()): string

Formats in UTC regardless of `date_default_timezone_set()`. Handled format characters, exactly: `d j D l N w S z W F M m n t L Y y a A g G h H i s U o u v c r`; a backslash escapes the next character. Every OTHER character — including the timezone specifiers `e T O P p Z I` — is emitted LITERALLY. `u`/`v` always render zeros, `c` always ends `+00:00`, and `r` always ends `+0000`.

echo date("Y-m-d H:i:s", 0);   // => 1970-01-01 00:00:00

# gmdate

gmdate(string $format, int $timestamp = time()): string

Shares the `date` arm — same UTC formatting, same exact set of supported format characters.

echo gmdate("Y-m-d", 0);   // => 1970-01-01

# checkdate

checkdate(int $month, int $day, int $year): bool

True when the month is 1-12, the year is 1-32767, and the day is within that month under the proleptic Gregorian leap rule. Missing arguments read as 0 and therefore fail.

var_dump(checkdate(2, 29, 2024), checkdate(2, 30, 2024));   // => bool(true) bool(false)

# microtime

microtime(bool $as_float = false): float|string

With a truthy argument the timestamp as a float; otherwise the classic `"0.12345678 1712345678"` string with the fraction first.

var_dump(microtime(true) > 0);   // => bool(true)

# strtotime

strtotime(string $datetime, int $baseTimestamp = time()): int|false

Parses only this SUBSET, all in UTC: the keywords `now`, `today`, `midnight`, `yesterday`, `tomorrow`; `@<int>` for explicit Unix seconds; the absolute forms `YYYY-MM-DD HH:MM:SS` and `YYYY-MM-DD`; and chained relative tokens matching `[+-]N (sec|min|hour|day|week|month|year)[s] [ago]`. Anything else — a month name, a slashed date, `next monday`, a timezone suffix — returns `false`. Month and year arithmetic uses PHP's OVERFLOWING day rule, so `2011-01-31 +1 month` lands in March.

echo strtotime("1970-01-02");   // => 86400

# getdate

getdate(int $timestamp = time()): array

A UTC-computed map with the keys `seconds minutes hours mday wday mon year yday weekday month` plus the integer key `0` holding the timestamp. DIVERGENCE: only key `0` is numeric — PHP's other numeric indices are absent.

echo getdate(0)["year"], getdate(0)["weekday"];   // => 1970Thursday

# date_default_timezone_set

date_default_timezone_set(string $timezoneId): true

Stores the name and ALWAYS returns true. DIVERGENCE: an unknown name is not rejected (PHP returns false), and setting a non-UTC zone shifts NOTHING — every calculation in the module stays UTC.

var_dump(date_default_timezone_set("America/New_York"));   // => bool(true)

# date_default_timezone_get

date_default_timezone_get(): string

The name last stored, defaulting to `UTC`. It round-trips whatever string was set, valid or not.

echo date_default_timezone_get();   // => UTC

Date and time — object API

# date_create

date_create(string $datetime = "now"): object

Constructs a prelude `DateTime`. DIVERGENCE: PHP's `$timezone` parameter does not exist.

echo date_create("@0")->format("Y");   // => 1970

# date_create_immutable

date_create_immutable(string $datetime = "now"): object

Constructs a prelude `DateTimeImmutable`, again with no `$timezone` parameter.

echo date_create_immutable("@0")->getTimestamp();   // => 0

# date_interval_create_from_date_string

date_interval_create_from_date_string(string $datetime): object

DIVERGENCE: it constructs a `DateInterval` from the EMPTY string — the `$datetime` argument is accepted and ignored, so the returned interval always carries a zero duration regardless of what you pass.

echo date_interval_create_from_date_string("1 day")->d;   // => 0

# date_format

date_format(object $object, string $format): string|false

Calls `->format($format)` on the receiver's own class. A non-object first argument returns `false`.

echo date_format(date_create("@0"), "Y-m-d");   // => 1970-01-01

# date_add

date_add(object $object, object $interval): mixed

Calls `->add($interval)`. On a `DateTime` this mutates and returns the receiver; on a `DateTimeImmutable` it returns a new instance.

echo date_add(date_create("@0"), new DateInterval("P1D"))->getTimestamp();   // => 86400

# date_sub

date_sub(object $object, object $interval): mixed

Calls `->sub($interval)` with the same mutable-versus-immutable behaviour as `date_add`.

echo date_sub(date_create("@86400"), new DateInterval("P1D"))->getTimestamp();   // => 0

# date_diff

date_diff(object $baseObject, object $targetObject): mixed

Calls `->diff($target)` and returns a `DateInterval`. DIVERGENCE: PHP's `$absolute` third parameter is not forwarded, so the interval's `invert` flag is always the one `diff` computes.

echo date_diff(date_create("@0"), date_create("@86400"))->d;   // => 1

# date_modify

date_modify(object $object, string $modifier): mixed

Calls `->modify($modifier)`, which routes through `strtotime` and therefore accepts only that function's documented subset.

echo date_modify(date_create("@0"), "+1 day")->getTimestamp();   // => 86400

# date_timestamp_get

date_timestamp_get(object $object): mixed

Calls `->getTimestamp()`, returning `false` for a non-object argument.

echo date_timestamp_get(date_create("@42"));   // => 42

# date_timestamp_set

date_timestamp_set(object $object, int $timestamp): mixed

Calls `->setTimestamp($timestamp)`. Only `DateTime` declares that method, so an immutable receiver has nothing to dispatch to.

echo date_timestamp_set(date_create("@0"), 99)->getTimestamp();   // => 99

# date_interval_format

date_interval_format(object $object, string $format): mixed

Calls `->format($format)` on a `DateInterval` — the identical code path as `date_format`, differing only in which class resolves the method.

echo date_interval_format(new DateInterval("P2D"), "%d");   // => 2

# date_offset_get

date_offset_get(object $object): int

DIVERGENCE: always returns 0 and ignores its argument, because the runtime is UTC-only and has no offset to report.

echo date_offset_get(date_create("@0"));   // => 0

By-reference array mutators

# array_pop

array_pop(array $array): mixed

Removes and returns the LAST element, shortening the caller's array in place. An empty array yields null.

$a = [1, 2, 3]; echo array_pop($a), count($a);   // => 32

# array_shift

array_shift(array $array): mixed

Removes and returns the FIRST element, reindexing the remaining integer keys from 0. An empty array yields null.

$a = [1, 2, 3]; echo array_shift($a), implode(",", $a);   // => 12,3

# array_unshift

array_unshift(array $array, mixed ...$values): int

Prepends the values, reindexing integer keys from 0, and returns the new element count.

$a = [2, 3]; array_unshift($a, 1); echo implode(",", $a);   // => 1,2,3

# array_splice

array_splice(array $array, int $offset, ?int $length = null, mixed $replacement = []): array

Removes a run of elements in place and optionally substitutes a replacement, returning the extracted run. Negative offsets and lengths count from the end.

$a = [1, 2, 3, 4]; array_splice($a, 1, 2); echo implode(",", $a);   // => 1,4

Built-in object methods

# Generator::current

$generator->current(): mixed

The value the generator is currently suspended on, running it up to its first `yield` if it has not started. Generator bodies run on their own native stack, so a `yield` deep inside nested calls suspends the whole VM in one stack switch.

function g() { yield 1; yield 2; } $x = g(); echo $x->current();   // => 1

# Generator::key

$generator->key(): mixed

The key of the current element — the auto-incrementing integer for a bare `yield`, or the explicit key from a `yield $k => $v`.

function g() { yield "k" => "v"; } $x = g(); echo $x->key();   // => k

# Generator::next

$generator->next(): void

Resumes the generator until its next `yield` or its return. Equivalent to `send(null)` with the result discarded.

function g() { yield 1; yield 2; } $x = g(); $x->next(); echo $x->current();   // => 2

# Generator::valid

$generator->valid(): bool

False once the body has run to completion, which is what ends a `foreach` over the generator.

function g() { yield 1; } $x = g(); $x->next(); var_dump($x->valid());   // => bool(false)

# Generator::rewind

$generator->rewind(): void

Runs the generator up to its first `yield` if it has not started. It cannot actually rewind an already-advanced generator, matching PHP.

# Generator::send

$generator->send(mixed $value): mixed

Resumes the generator, making the suspended `yield` expression evaluate to `$value`, and returns the next yielded value. A missing argument sends null.

function g() { $a = yield 1; echo "got:$a"; }
$x = g(); echo $x->current(); $x->send("Z");   // => 1got:Z

# Generator::throw

$generator->throw(object $exception): mixed

Resumes the generator by raising the exception AT the suspended `yield`, so a `try`/`catch` inside the body can handle it.

function g() { try { yield 1; } catch (Exception $e) { echo "C:" . $e->getMessage(); } }
$x = g(); $x->current(); $x->throw(new Exception("boom"));   // => C:boom

# Generator::getReturn

$generator->getReturn(): mixed

The value the generator body returned. It reads the stored slot directly and does NOT raise when the generator has not finished — it simply reports whatever is there, which differs from PHP's `Exception`.

function g() { yield 1; return 9; } $x = g(); $x->next(); echo $x->getReturn();   // => 9

# Closure::bind

Closure::bind(Closure $closure, ?object $newThis, object|string|null $newScope = null): Closure

Static form of `bindTo`: returns a copy of the closure with `$this` rebound and, when a scope is given, private-member access granted for that class.

class C { private $p = 7; }
$f = function () { return $this->p; };
echo Closure::bind($f, new C, C::class)();   // => 7

# Closure::fromCallable

Closure::fromCallable(callable $callback): Closure

DIVERGENCE: it returns its argument UNCHANGED rather than wrapping it. That works because a callable string is already dispatchable through the runtime's `call_value`, but it means `Closure::fromCallable("strlen")` yields the string `"strlen"`, not a `Closure` — so `is_object()` on the result is false.

$c = Closure::fromCallable("strlen"); echo $c("abcd");   // => 4

# Closure::bindTo

$closure->bindTo(?object $newThis, object|string|null $newScope = null): Closure

Returns a copy of the closure with `$this` rebound. Both arguments are optional; omitting the object unbinds `$this`.

class C { public $p = 3; }
$f = function () { return $this->p; };
echo $f->bindTo(new C)();   // => 3

# Closure::call

$closure->call(object $newThis, mixed ...$args): mixed

Binds and invokes in one step. The scope is ALWAYS the bound object's own class, so the closure can reach that class's private members without naming a scope.

class C { private $p = 8; }
$f = function () { return $this->p; };
echo $f->call(new C);   // => 8

# enum::cases

EnumName::cases(): array

A list of every case object of the enum, in declaration order. Synthesized per enum rather than declared, so it exists on both pure and backed enums.

enum S: string { case A = "a"; case B = "b"; } echo count(S::cases());   // => 2

# enum::from

EnumName::from(string|int $value): EnumName

The case whose backing value equals the argument. An unmatched value is an error — use `tryFrom` for the null-returning form.

enum S: string { case A = "a"; } echo S::from("a")->name;   // => A

# enum::tryFrom

EnumName::tryFrom(string|int $value): ?EnumName

The case whose backing value equals the argument, or null when none matches. It shares `from`'s implementation with the error path turned into a null return.

enum S: string { case A = "a"; } var_dump(S::tryFrom("z"));   // => NULL

# enum->name

$case->name: string

The declared case name, present on every enum case — pure and backed alike.

enum S { case A; } echo S::A->name;   // => A

# enum->value

$case->value: string|int

The backing value of a case. Present only on a backed enum (one declared with a `: string` or `: int` backing type); a pure enum's cases carry only `name`.

enum S: string { case A = "a"; } echo S::A->value;   // => a

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)

Output buffering

# ob_start

ob_start(?callable $callback = null, int $chunk_size = 0, int $flags = 0): true

Pushes a new buffer onto the stack, so buffers nest correctly. DIVERGENCE: `$callback`, `$chunk_size`, and `$flags` are NEVER read — the output-rewriting callback does not run at all and there is no chunked auto-flush. Always returns true.

ob_start(); echo "hidden"; $s = ob_get_clean(); echo strlen($s);   // => 6

# ob_get_contents

ob_get_contents(): string|false

The top buffer's contents WITHOUT popping it, or `false` when no buffer is active.

ob_start(); echo "ab"; $s = ob_get_contents(); ob_end_clean(); echo $s;   // => ab

# ob_get_clean

ob_get_clean(): string|false

Pops the top buffer and returns its contents, discarding them from the output. `false` when no buffer is active.

ob_start(); echo "x"; echo ob_get_clean();   // => x

# ob_end_clean

ob_end_clean(): bool

Pops and DISCARDS the top buffer, returning whether one was active.

ob_start(); echo "gone"; var_dump(ob_end_clean());   // => bool(true)

# ob_end_flush

ob_end_flush(): bool

Pops the top buffer and writes its contents DOWN ONE LEVEL — into the enclosing buffer if there is one, otherwise to output. Returns whether a buffer was active.

ob_start(); echo "out"; ob_end_flush();   // => out

# ob_get_flush

ob_get_flush(): string|false

Reads the top buffer's contents and then performs `ob_end_flush()`, so the text is both returned AND written down one level.

ob_start(); echo "y"; $s = ob_get_flush(); echo "|$s";   // => y|y

# ob_flush

ob_flush(): null

Writes the top buffer's contents one level down and leaves the level ACTIVE but cleared. The host's success flag is discarded, so calling it with no active buffer fails invisibly.

ob_start(); echo "a"; ob_flush(); echo "b"; ob_end_clean();   // => a

# ob_get_level

ob_get_level(): int

The genuine nesting depth of the buffer stack.

ob_start(); $n = ob_get_level(); ob_end_clean(); echo $n;   // => 1

# ob_get_length

ob_get_length(): int|false

The top buffer's length in BYTES, not characters. `false` when no buffer is active.

ob_start(); echo "abc"; $n = ob_get_length(); ob_end_clean(); echo $n;   // => 3

# flush

flush(): null

DIVERGENCE: a complete no-op. It does not flush stdout and does not touch the buffer stack.

var_dump(flush());   // => NULL

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)

Runtime and diagnostics

# assert

assert(mixed $assertion, mixed $description = null): bool

Evaluates the assertion for truthiness and RETURNS it. DIVERGENCE: this inverts PHP 8's contract — PHP always returns true and throws `AssertionError` on failure, whereas here `assert(false)` simply returns false and throws nothing. `$description` is never read.

var_dump(assert(1 === 1), assert(false));   // => bool(true) bool(false)

# assert_options

assert_options(int $option, mixed $value = null): int

DIVERGENCE: a stub that always returns 0 and stores nothing. No argument is read.

echo assert_options(1);   // => 0

# trigger_error

trigger_error(string $message, int $error_level = E_USER_NOTICE): bool

Prints `"<label>: <message>"` to STDERR and returns true. The label is `Fatal error` for `E_USER_ERROR`, `Warning` for `E_USER_WARNING`, `Deprecated` for `E_USER_DEPRECATED`, and `Notice` for everything else. DIVERGENCE: `E_USER_ERROR` does NOT halt — execution continues with the next statement.

trigger_error("careful", E_USER_WARNING); echo "still running";   // => still running

# user_error

user_error(string $message, int $error_level = E_USER_NOTICE): bool

The alias of `trigger_error`, sharing its match arm and its non-halting `E_USER_ERROR` behaviour.

# error_log

error_log(string $message, int $message_type = 0, string $destination = ""): bool

With `$message_type` 3 AND a destination it really appends the raw message bytes to that file — no timestamp and no trailing newline are added. Every other combination writes the message to stderr and returns true. The email and SAPI-logger message types are not implemented.

error_log("note", 3, "/tmp/php_ref_log.txt"); echo file_get_contents("/tmp/php_ref_log.txt");   // => note

# debug_backtrace

debug_backtrace(int $options = 0, int $limit = 0): array

DIVERGENCE: a stub that always returns an empty array. No call stack is captured, so neither argument has any effect.

echo count(debug_backtrace());   // => 0

# debug_print_backtrace

debug_print_backtrace(int $options = 0, int $limit = 0): null

DIVERGENCE: a stub that prints nothing and returns null.

var_dump(debug_print_backtrace());   // => NULL

# set_error_handler

set_error_handler(?callable $callback, int $error_levels = E_ALL): null

DIVERGENCE: accepted and discarded. There is no error-handler chain, so the callback is NEVER invoked; it returns null, which reads as "no previous handler".

var_dump(set_error_handler(fn() => true));   // => NULL

# set_exception_handler

set_exception_handler(?callable $callback): null

DIVERGENCE: shares the `set_error_handler` arm — the callback is discarded, so an uncaught exception still ends the program with the standard `Fatal error: Uncaught …` block instead of being routed to the handler.

# restore_error_handler

restore_error_handler(): bool

DIVERGENCE: a no-op that always returns true, since no handler was ever stored.

var_dump(restore_error_handler());   // => bool(true)

# restore_exception_handler

restore_exception_handler(): bool

DIVERGENCE: a no-op returning true, sharing the `restore_error_handler` arm.

# register_shutdown_function

register_shutdown_function(callable $callback, mixed ...$args): null

DIVERGENCE: the callback is accepted and SILENTLY DROPPED — no shutdown queue exists, so it never runs. Put cleanup in a `finally` block instead.

var_dump(register_shutdown_function(fn() => print("never")));   // => NULL

# spl_autoload_register

spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool

DIVERGENCE: a no-op returning true. There is no autoload chain and no `include`, so every class must be declared in the source that uses it.

var_dump(spl_autoload_register(fn($c) => null));   // => bool(true)

# spl_autoload_unregister

spl_autoload_unregister(callable $callback): bool

DIVERGENCE: a no-op returning true.

# get_defined_vars

get_defined_vars(): array

The current frame's bound variables, in the order they were first bound. An unset name is absent; a name bound to `null` is present. `$this` and `$GLOBALS` are not included. PARTIAL DIVERGENCE at GLOBAL scope only: the reference lists the superglobals its `variables_order` actually populated, in ITS fixed order and ahead of the script's own variables, where this lists every superglobal the frame holds in binding order. Inside a function — where the answer is the user's own variables — it matches.

function f($p) { $a = 1; unset($a); return get_defined_vars(); } print_r(f(7));   // => [p => 7]

# class_alias

class_alias(string $class, string $alias, bool $autoload = true): bool

DIVERGENCE: it reports whether `$class` exists and REGISTERS NOTHING — `$alias` is never read. A true return is therefore misleading: instantiating the alias afterwards still fails.

class C {} var_dump(class_alias("C", "D"), class_exists("D"));   // => bool(true) bool(false)

Callables

# call_user_func

call_user_func(callable $callback, mixed ...$args): mixed

Invokes any of the five accepted callable forms with the remaining arguments: a `Closure`, a `"function"` name, a `"Class::method"` string, `[$obj, "method"]`, or `["Class", "method"]`. Method names resolve case-insensitively.

echo call_user_func("strtoupper", "hi");   // => HI

# call_user_func_array

call_user_func_array(callable $callback, array $args): mixed

Spreads the array's VALUES in order, discarding keys — so named-argument spreading is not modelled. A non-array second argument raises the reference's `Argument #2 ($args) must be of type array` TypeError. This is also what first-class callable syntax `f(...)` desugars to.

echo call_user_func_array("max", [1, 5, 3]);   // => 5

# function_exists

function_exists(string $function): bool

Exact for USER functions, which are looked up in the real function table. DIVERGENCE: builtin coverage is a hand-maintained allow-list of 314 names, so it is wrong in both directions — an implemented function absent from the list (`assert`, `error_log`, `class_alias`, `debug_backtrace`, `filter_has_var`, …) reports false, while `interface_exists`, `trait_exists`, and `enum_exists` report true even though all three are hardcoded stubs.

function myFn() {} var_dump(function_exists("myFn"), function_exists("strlen"));   // => bool(true) bool(true)

Constants

# define

define(string $constant_name, mixed $value, bool $case_insensitive = false): bool

Inserts a constant, returning false when the name already exists — including any of the 156 predefined ones. DIVERGENCE: a failed redefinition prints no warning, the third argument is never read, and an omitted value stores null.

define("PHP_REF_C", 5); echo PHP_REF_C;   // => 5

# defined

defined(string $constant_name): bool

Whether a constant of that exact name exists. Lookup is case-SENSITIVE, unlike class and function lookup, so `define("FOO", 1)` leaves `defined("foo")` false.

define("PHP_REF_D", 1); var_dump(defined("PHP_REF_D"), defined("php_ref_d"));   // => bool(true) bool(false)

# constant

constant(string $name): mixed

The value of the named constant. An UNDEFINED name throws `Error: Undefined constant "<name>"`, catchable, exactly as a bare reference to the same name does.

echo constant("NOPE");   // => NOPE

Input filtering

# filter_var

filter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed

Applies one filter. `$options` may be a bare flags bitmask or a `['flags' => …, 'options' => …]` map. A failing VALIDATOR returns false, or null when `FILTER_NULL_ON_FAILURE` is set; sanitizers cannot fail. DIVERGENCE: an ARRAY value fails every filter; `FILTER_CALLBACK`, `FILTER_SANITIZE_ENCODED`, and `FILTER_SANITIZE_ADD_SLASHES` are unimplemented and silently return the raw string; and `options['default']` is never consulted.

var_dump(filter_var("42", FILTER_VALIDATE_INT), filter_var("x", FILTER_VALIDATE_INT));   // => int(42) bool(false)

# filter_var_array

filter_var_array(array $array, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false

Filters many fields at once. With a definition array the output is keyed by the DEFINITION — data keys it does not mention are dropped, and fields missing from the data become null. With a bare id, that filter applies to every field. A non-array subject returns false. DIVERGENCE: `$add_empty` is never read, so missing fields are always emitted.

print_r(filter_var_array(["a" => "1"], ["a" => FILTER_VALIDATE_INT]));   // => Array\n(\n    [a] => 1\n)

# filter_has_var

filter_has_var(int $input_type, string $var_name): bool

DIVERGENCE: hardcoded `false`, reading neither argument. phplang is a standalone runtime with no request context, so there is no `$_GET` or `$_POST` to consult.

var_dump(filter_has_var(INPUT_GET, "q"));   // => bool(false)

More