← All docs

System & I/O

System functions, date/time, JSON, filesystem utilities, process execution, and debugging utilities.

System functions

FunctionSignatureDescription
exit()exit($code = 0): voidTerminate program
die()die($code = 0): voidAlias for exit()
time()time(): intUnix timestamp
microtime()microtime($as_float = false): string|floatCurrent time with microsecond precision. Returns the "0.NNNNNNNN SSSSSSSSSS" string (fractional microseconds as 8 digits, a space, then Unix seconds) by default or when $as_float is false; returns seconds as a float when $as_float is true. A non-literal flag yields string|float (boxed Mixed), resolved at runtime.
hrtime()hrtime($as_number = false): array|intHigh-resolution monotonic time (CLOCK_MONOTONIC). Returns [seconds, nanoseconds], or the total nanoseconds as an int when $as_number is true — for benchmarking elapsed time.
sleep()sleep($seconds): intSleep for seconds
usleep()usleep($microseconds): voidSleep for microseconds
getenv()getenv($name = null, $local_only = false): string|array|falseGet one environment variable, or — with no argument — the whole environment as a string-keyed array. Answers false for a name that is not set, and "" for one set to the empty string. $local_only is accepted and has no effect: there is no environment here separate from the process’s
putenv()putenv($assignment): boolSet an environment variable (KEY=VALUE), or remove it when the argument has no =
define()define($name, $value): boolDefine a compile-time global constant with a string-literal name
defined()defined($name): boolCheck whether a string-literal constant name is defined
constant()constant($name): mixedValue of a global constant named by a string literal. AOT has no runtime constant table, so a dynamic name, a Foo::BAR class constant, and an unknown name are compile errors
php_uname()php_uname($mode = "a"): stringGet system information from the target runtime
phpversion()phpversion(?string $extension = null): string|falseGet the targeted PHP language version, or one extension’s version (false if it is not loaded)
zend_version()zend_version(): stringGet the Zend Engine version for the compile target
php_sapi_name()php_sapi_name(): stringGet the SAPI name ("cli", or "cli-server" under --web)
ini_restore()ini_restore(string $option): voidRestore a directive to its startup value — a no-op, see below
exec()exec($command): stringExecute command, return output
shell_exec()shell_exec($command): stringExecute via shell, return output
system()system($command): stringExecute, output to stdout
passthru()passthru($command): voidExecute, pass raw output

The environment and the CLI superglobals

getenv() reads the process environment live, so a putenv() made earlier in the same program is visible to it:

putenv("APP_MODE=debug");
echo getenv("APP_MODE");        // debug
putenv("APP_MODE");             // a bare name REMOVES the variable
var_dump(getenv("APP_MODE"));   // bool(false)

Called with no argument it answers the whole environment as a string-keyed array, and the optional $local_only argument is accepted and has no effect — in the CLI SAPI there is no environment separate from the process’s, so both forms agree:

$env = getenv();
echo count($env), "\n";
echo $env["PATH"], "\n";

A value containing = is split on the first one, as PHP does, so a variable whose value holds an = keeps its name and its whole value.

$_ENV and $_SERVER

Both superglobals carry the environment in an ordinary compiled CLI program, and $_SERVER carries PHP’s own CLI keys on top of it:

KeyValue
argvthe program’s arguments, like $argv
argcthe argument count, like $argc
PHP_SELF, SCRIPT_NAME, SCRIPT_FILENAME, PATH_TRANSLATED$argv[0] — a compiled program has no script at run time, so the thing that was actually invoked is the closest true answer
DOCUMENT_ROOT""
REQUEST_TIME, REQUEST_TIME_FLOATthe start time, as time() and microtime(true)

The five request superglobals ($_GET, $_POST, $_COOKIE, $_FILES, $_REQUEST) stay empty arrays, exactly as they are under php on the command line. Under --web $_SERVER describes the HTTP request instead.

Seeding is pay-for-use: only the superglobals a program actually spells are built, which is what PHP’s auto_globals_jit does for the same reason.

$_ENV and $_SERVER are snapshots taken before the program ran, so a later putenv() does not reach them — only getenv(). PHP has the same asymmetry:

putenv("LATE=1");
var_dump(getenv("LATE"));        // string(1) "1"
var_dump(isset($_ENV["LATE"]));  // bool(false)

Known limitation

Reading an element of the nested $_SERVER['argv'] does not work: the index returns a raw pointer as an int and foreach over it iterates zero times, while count($_SERVER['argv']) is correct. This is a general limitation of a typed array held inside a mixed value rather than anything specific to argv, and it predates the CLI superglobals. Read $argv directly, which is unaffected:

foreach ($argv as $i => $arg) {   // correct
    echo $i, ": ", $arg, "\n";
}

PHP version surface

elephc targets a PHP language profile selected by --php-version (8.2/8.3/8.4/8.5, default 8.5), not a specific upstream patch release. The whole version surface therefore reports 8.<minor>.0:

Symbol--php-version 8.5--php-version 8.2
PHP_VERSION"8.5.0""8.2.0"
PHP_VERSION_ID8050080200
PHP_MAJOR_VERSION88
PHP_MINOR_VERSION52
PHP_RELEASE_VERSION00
PHP_EXTRA_VERSION""""
phpversion()"8.5.0""8.2.0"
zend_version()"4.5.0""4.2.0"

PHP_VERSION_ID uses PHP’s formula, major * 10000 + minor * 100 + release (reference PHP 8.5.6 reports 80506), so the id and the string always agree.

This is the same rule the OPcache surface already applies: opcache_get_configuration()['version']['version'] reports 8.5.0 too, and the two are guaranteed to match inside one binary.

Divergence from reference PHP. Reference PHP 8.5.6 reports 8.5.6, 80506, PHP_RELEASE_VERSION 6 and Zend 4.5.6. elephc has no patch release to report — there is no PHP runtime inside the compiled binary — so the patch component is 0. Feature detection is unaffected: PHP_VERSION_ID >= 80500 answers exactly the question --php-version 8.5 answers. PHP_EXTRA_VERSION and PHP_MAJOR_VERSION/PHP_MINOR_VERSION match reference exactly.

Why understate rather than claim the reference patch. elephc’s observable surface is verified against a specific reference build, but that is not the same as shipping that build’s bug fixes: elephc is a reimplementation, not a PHP runtime, so “our behavior matches 8.5.6” does not license the claim “we contain every fix through 8.5.6”. The two directions fail differently, and only one of them fails safely:

ReportedCode gating >= 8.5.6Consequence
8.5.0takes the OLD branch, applies a workaroundredundant work, harmless
8.5.6takes the NEW branch, assumes a fix is presentbreaks if elephc lacks it

Understating makes callers do unnecessary work; overstating makes them skip protections. The cost is real and worth knowing: a caller gating on a PATCH version (version_compare(PHP_VERSION, '8.5.6', '>=')) sees this binary as older than the behavior it actually implements. Gating on a MINOR version — by far the common case — is unaffected. If elephc ever tracks patch-level fixes explicitly, this choice should be revisited.

phpversion($extension) returns that same version string for a loaded extension and false for anything else, matching reference PHP, where every bundled extension reports the interpreter’s own version. Membership is exactly extension_loaded()’s — the same core set plus the bridges this binary links — so phpversion($e) !== false and extension_loaded($e) always agree. Names are compared case-insensitively, as in reference PHP.

PHP_SAPI / php_sapi_name()

Compile modeReported
default (CLI binary)"cli"
--web / --with-web"cli-server"

cli matches reference PHP exactly. cli-server is elephc’s documented choice for --web: the binary embeds its own HTTP listener with no external web server, no FastCGI channel and no module host, which is precisely what reference PHP’s built-in server is — and it is the only reference SAPI name that describes a standalone PHP binary speaking HTTP. It matters because library code gates on PHP_SAPI === 'cli' to tell a console run from a request; reporting cli under --web would put every such library on the console path inside an HTTP request.

Known caveat. In reference PHP, cli-server is the development server, and some libraries treat that name as a signal to enable development behavior — verbose error pages, disabled caching, relaxed static-file handling. A --web binary is not a development server, so a library keying on that connotation may behave more loosely than intended. The alternative — inventing a SAPI name outside php-src’s vocabulary — trades this for a value no library recognizes at all, and would break code that validates PHP_SAPI against the known set. If a deployment hits the development-mode reading in practice, that trade is worth reopening; the console-detection idiom, which is the dominant use by a wide margin, answers correctly either way.

ini_restore()

ini_restore() is a no-op returning void (reference PHP returns void too). Every INI value in elephc is baked into the binary at compile time and nothing can change it at runtime — ini_set() already reports failure for every key for that reason — so a directive is always already at its startup value. “Restore it to the startup value” is therefore an exact no-op, not an approximation.

Availability

zend_version(), php_sapi_name() and ini_restore() are pay-for-use: they are declared only in binaries whose source mentions them, exactly like ini_get()/ini_set()/ini_get_all(). A program that declares its own function of the same name keeps it.

Inside eval(), the interpreter has no access to --php-version or --web and reports the default profile: PHP_VERSION "8.5.0", PHP_SAPI "cli". On a default-profile CLI binary that is identical to the compiled surface.

define() returns true the first time a constant is defined at runtime. Duplicate attempts keep the first value, return false, and emit a suppressible runtime warning. defined() currently requires a string literal in AOT mode.

php_uname() supports PHP’s standard one-character modes:

ModeResult
"a"Full system line: system name, node name, release, version, machine
"s"System name, matching PHP_OS ("Darwin" on macOS targets, "Linux" on Linux targets)
"n"Network node name
"r"Release
"v"Version
"m"Machine hardware name

Date and time

FunctionSignatureDescription
date()date($format [, $timestamp]): stringFormat a timestamp (or the current time) in the default timezone using the specifiers below.
gmdate()gmdate($format [, $timestamp]): stringSame as date() but formats in UTC, so the result is independent of the configured timezone.
idate()idate($format [, $timestamp]): intLike date() for a single integer-valued specifier, returning an int instead of a string (e.g. idate("Y"), idate("U")). Equivalent to (int) date($format, $timestamp).
date_default_timezone_set()date_default_timezone_set($timezoneId): boolSet the default timezone used by date() (and DateTime::format()). Accepts any IANA identifier (e.g. "Europe/Paris", "UTC"); the UTC offset and daylight-saving transitions are resolved from the system timezone database. Returns true.
date_default_timezone_get()date_default_timezone_get(): stringReturn the current default timezone identifier (defaults to "UTC" when none has been set).
mktime()mktime($h, $m, $s, $mon, $day, $yr): intCreate a timestamp from components, interpreted in the default timezone.
gmmktime()gmmktime($h, $m, $s, $mon, $day, $yr): intLike mktime() but interprets the components as UTC (independent of the default timezone).
checkdate()checkdate($month, $day, $year): boolValidate a Gregorian date — leap-year aware; month 1–12, day within the month, year 1–32767.
getdate()getdate($timestamp = time()): arrayDecompose a timestamp into an associative array: seconds, minutes, hours, mday, wday, mon, year, yday, weekday, month, and 0 (the timestamp).
localtime()localtime($timestamp = time(), $associative = false): arrayDecompose a timestamp into the raw struct tm fields — numeric-indexed 08 by default, or tm_sectm_isdst keys when $associative is true (tm_mon is 0-based, tm_year is years since 1900).
gettimeofday()gettimeofday($as_float = false): array|floatCurrent time as ['sec' => int, 'usec' => int, 'minuteswest' => int, 'dsttime' => int] (minuteswest/dsttime from the default zone), or a float of seconds when $as_float is true. usec is derived from microtime().
strftime() / gmstrftime()strftime($format [, $timestamp]): stringFormat a timestamp with C strftime %-specifiers (deprecated in PHP 8.1). Specifiers match PHP exactly, including the week numbers %U (Sunday-based), %W (Monday-based) and %V (ISO), the space-padded %e/%k/%l, %c (with its space-padded day), and the two-digit ISO year %g. %x/%X use the default C-locale forms (%m/%d/%y, %H:%M:%S). One known difference: %P yields lowercase am/pm here, whereas PHP’s strftime emits a literal P (it never implemented the GNU lowercase extension). gmstrftime() formats in UTC.
strptime()strptime($timestamp, $format): array|falseInverse of strftime(): parse a string against C strftime %-specifiers (%Y %y %m %d %e %H %M %S %j %B %b %h %A %a %p %P, the week specifiers %u %w %U %W %V and the timezone specifiers %z/%Z (consumed but not used to build the instant), plus %n/%t and %%) into a struct tm array (tm_sec/tm_min/tm_hour/tm_mday/tm_mon 0-based/tm_year since 1900/tm_wday/tm_yday/unparsed), or false on mismatch. Deprecated in PHP 8.1.
strtotime()strtotime($datetime [, $baseTimestamp]): int|falseParse a date/time string into a Unix timestamp. Supports ISO dates, time-only, relative offsets, named weekdays, and bare keywords. When $baseTimestamp is given, relative/keyword/time-only forms resolve against it instead of the current time. An ISO field outside its range (month > 12, day > 31, hour > 24, minute > 59, second > 60) returns false, matching PHP; in-range calendar overflow such as "2026-02-30" is still normalized. Returns false on failure (use === false; -1 is the valid timestamp one second before the epoch).

When date_default_timezone_set() has not been called, the default timezone is UTC (matching PHP), so date(), strtotime(), mktime(), and DateTime produce host-independent output rather than following the build machine’s local zone.

Procedural date/time aliases (date_create, date_create_immutable, date_create_from_format, date_create_immutable_from_format, date_diff, date_format, date_add, date_sub, date_modify, date_timestamp_get, date_timestamp_set, date_timezone_get, date_timezone_set, date_offset_get, date_date_set, date_isodate_set, date_time_set, date_interval_format, date_interval_create_from_date_string, date_parse, date_parse_from_format, date_get_last_errors, date_sun_info, date_sunrise, date_sunset, strptime, idate, gettimeofday, strftime, gmstrftime, timezone_open, timezone_identifiers_list, timezone_name_get, timezone_offset_get, timezone_name_from_abbr, timezone_location_get, timezone_transitions_get, timezone_abbreviations_list, timezone_version_get) are recognized by function_exists() even though the name resolver rewrites them into OOP calls or built-in expressions. The comparison is case-insensitive and accepts one leading \ (function_exists('\idate') is true), but the aliases live in the global namespace only, so a qualified spelling such as '\foo\bar\idate' is false, as in PHP. The name may be a variable: a literal const-folds and any other string expression is matched at run time against the same set.

date() and gmdate() support these format specifiers (any other character is copied through verbatim):

SpecifierOutput
Y / y4-digit year / 2-digit year
m / nmonth 01–12 / 1–12
d / jday of month 01–31 / 1–31
D / lshort weekday name (Mon) / full weekday name (Monday)
N / wISO weekday 1–7 (Mon=1) / numeric weekday 0–6 (Sun=0)
F / Mfull month name (January) / short month name (Jan)
H / Ghour 00–23 / 0–23
h / ghour 01–12 / 1–12
i / sminutes 00–59 / seconds 00–59
A / aAM/PM / am/pm
SEnglish ordinal suffix for the day of month (st, nd, rd, th)
zday of year, 0–365
W / oISO-8601 week number (01–53) / ISO-8601 week-numbering year
tnumber of days in the month, 28–31
Lleap year flag, 1 or 0
UUnix timestamp
O / PUTC offset ±hhmm (+0200) / ±hh:mm (+02:00)
plike P, but the literal Z when the offset is zero
ZUTC offset in seconds, -4320050400 (7200, -18000)
BSwatch Internet Time, 000999 beats of the UTC+1 day
T / etimezone abbreviation (CEST) / identifier (Europe/Paris); gmdate() reports UTC
Idaylight-saving flag, 1 if DST is in effect at the instant, else 0
c / rISO 8601 datetime (2024-07-01T14:00:00+02:00) / RFC 2822 datetime (Mon, 01 Jul 2024 14:00:00 +0200)
u / vmicroseconds / milliseconds; always 000000 / 000 (whole-second timestamps)
X / xexpanded ISO-8601 year — X is always signed (+2024); x is signed only outside [0, 9999] (1970, +10000, -0005)

A backslash escapes the next character so it is emitted literally instead of being treated as a specifier (e.g. date('Y-m-d\TH:i:s')2023-11-14T22:13:20). Use single-quoted format strings so PHP’s double-quoted escapes (\t, \n, …) don’t interfere. (A lone trailing backslash emits nothing, rather than PHP’s NUL byte.)

strtotime() accepts the following shapes (input is case-insensitive for keywords/unit names/weekday names, and leading/trailing ASCII whitespace is trimmed):

  • ISO date / datetimeYYYY-MM-DD, YYYY-MM-DD HH:MM, YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM, or YYYY-MM-DDTHH:MM:SS. Lowercase t is also accepted as the date/time separator.
  • @<timestamp> — a UNIX timestamp (e.g. "@1700000000"); an optional sign and a truncated fractional part are accepted. Returned verbatim (UTC), independent of the current time.
  • American M/D/YMM/DD/YYYY or single-digit M/D/Y, with an optional HH:MM[:SS] time suffix (e.g. "12/25/2024", "6/15/2024 8:05"). A 2-digit year windows to 2000–2069 (069) or 1970–1999 (7099). Month must be <= 12 and day <= 31.
  • Textual datesD Month Y ("25 December 2024") or Month D[,] Y ("December 25, 2024", "July 4 2024"), with full or 3-letter month names (case-insensitive) and an optional HH:MM[:SS] time suffix. The day is not range-checked, so "31 feb 2024" normalizes to March 2 as in PHP. A 2-digit year follows the same windowing as slash dates.
  • Bare keywordsnow, today, tomorrow, yesterday, midnight, noon. (midnight is an alias for today.)
  • Time-onlyH:MM, HH:MM, H:MM:SS, HH:MM:SS — combined with today’s date.
  • Relative offsets[+-]?N unit [N unit ...], a/an unit, and N unit ago / a/an unit ago (negates the whole expression). Units: sec(s), second(s), min(s), minute(s), hour(s), day(s), week(s), month(s), year(s). Composite forms like "+1 day 2 hours", "an hour", and "a day ago" are supported. Day/week offsets honor DST through libc mktime normalization.
  • Relative unitsthis <unit> (no change), next <unit> (+1), and last <unit> (-1) for the units second, minute, hour, day, week, month, year (e.g. "next month", "last year", "this hour", "next week"), preserving the time of day with calendar arithmetic. The week unit is Monday-anchored, matching PHP.
  • Named weekdaysMonday..Sunday and 3-letter abbreviations Mon..Sun. Modifiers: next <weekday> (next future occurrence; today + 7 if today matches), last <weekday> (most recent past; today - 7 if today matches), this <weekday> (delta may be zero when today matches). Result is midnight of the target day.
  • first/last day of <modifier> monththis, next, last/previous/prev select the month; first day of sets the 1st and last day of the final day. The time of day is preserved (e.g. "last day of next month").
  • <ordinal> <weekday> of <modifier> monthfirst..fifth or last of a named weekday (e.g. "first monday of next month", "last friday of this month", "third tuesday of next month"). A fifth occurrence that overflows rolls into the next month; the time resets to midnight.

An ISO 8601 datetime may carry a trailing explicit timezone: a numeric UTC offset +HH:MM, -HH:MM, +HHMM (optionally space-separated), Z, or the words UTC/GMT (e.g. "2024-06-15T12:00:00+02:00", "2024-06-15 12:00:00 +0200", "...Z", "... UTC"). The wall-clock is then interpreted at that offset (Z/UTC/GMT = UTC offset 0), overriding the configured default zone. A trailing IANA zone name is also accepted (e.g. "2024-06-15 12:00:00 America/New_York"): the date/time is interpreted in that zone with full daylight-saving handling resolved from the system timezone database (so the example is 12:00 EDT = 16:00 UTC), and the previous default zone is restored afterwards. The zone name is the final space-separated token, distinguished from the time by containing a letter, so a bare "YYYY-MM-DD HH:MM:SS" is unaffected. Bare ordinal weekdays without of <month> ("first monday") are not accepted (use next monday or the of <month> form). Malformed input returns false (use === false to detect failure). Pre-1900 year handling is described under Date and Time.

JSON

FunctionSignatureDescription
json_encode()json_encode($value, $flags = 0, $depth = 512): string|falseEncode as JSON. Supports int, float, string, bool, null, arrays, mixed payloads, and objects (public properties + JsonSerializable::jsonSerialize() dispatch). Multibyte UTF-8 characters are escaped to lowercase-hex \uXXXX by default (é\u00e9, surrogate pairs for codepoints ≥ U+10000), while the JSON_HEX_* replacements use PHP’s literal uppercase-hex forms (<\u003C). $flags observes JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, JSON_PRETTY_PRINT, JSON_FORCE_OBJECT, JSON_NUMERIC_CHECK, JSON_PRESERVE_ZERO_FRACTION, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT, JSON_PARTIAL_OUTPUT_ON_ERROR, and JSON_THROW_ON_ERROR (Inf/NaN trigger JSON_ERROR_INF_OR_NAN, and $depth overrun triggers JSON_ERROR_DEPTH; the throw flag promotes both to JsonException, while partial-output keeps the substituted JSON string). $depth defaults to 512 and is enforced for every container encoder (assoc arrays, indexed arrays, objects). The remaining JSON_INVALID_UTF8_* flags are accepted and observed for malformed UTF-8 strings.
json_decode()json_decode($json, $associative = null, $depth = 512, $flags = 0): mixedFull structural decoder. Returns a boxed Mixed cell whose runtime tag matches the decoded JSON value (null/bool/int/float/string/array/object). For JSON objects, $associative selects the shape: the PHP default (null/false) returns a stdClass instance whose properties are accessible with $obj->name; true returns an associative array indexable with $obj["name"]. Property access on the decoded Mixed is supported directly — codegen unboxes the cell, checks the stdClass class_id, and routes through the dynamic-property hash. $depth is enforced (JSON_ERROR_DEPTH on overflow). Failed decodes record PHP 8.6-style one-based line/column data for json_last_error_msg() and JSON_THROW_ON_ERROR. $flags observes JSON_THROW_ON_ERROR (raises JsonException on syntax/depth failure) and JSON_BIGINT_AS_STRING (integer tokens overflowing PHP_INT return as preserved-digit strings instead of wrapping through __rt_atoi).
json_last_error()json_last_error(): intReturns the runtime’s last JSON error code (JSON_ERROR_*).
json_last_error_msg()json_last_error_msg(): stringReturns the PHP-compatible message for json_last_error() (e.g. "No error", "Syntax error"). After json_decode() failures, syntax/control-character/depth/UTF-8/UTF-16 messages include a " near location line:column" suffix while numeric error codes stay unchanged.
json_validate()json_validate($json, $depth = 512, $flags = 0): boolRFC 8259 validator. Returns whether $json is syntactically valid, sets json_last_error() on failure, and accepts only 0 or JSON_INVALID_UTF8_IGNORE for $flags (matching PHP 8.3).

Constants

The full PHP JSON_* family is exposed and can be combined with the bitwise OR operator to build flag arguments.

Encoding flagsValueDecoding flagsValue
JSON_HEX_TAG1JSON_OBJECT_AS_ARRAY1
JSON_HEX_AMP2JSON_BIGINT_AS_STRING2
JSON_HEX_APOS4
JSON_HEX_QUOT8
JSON_FORCE_OBJECT16
JSON_NUMERIC_CHECK32
JSON_UNESCAPED_SLASHES64
JSON_PRETTY_PRINT128
JSON_UNESCAPED_UNICODE256
JSON_PARTIAL_OUTPUT_ON_ERROR512
JSON_PRESERVE_ZERO_FRACTION1024
JSON_INVALID_UTF8_IGNORE1048576
JSON_INVALID_UTF8_SUBSTITUTE2097152
JSON_THROW_ON_ERROR4194304
Error codeValueError codeValue
JSON_ERROR_NONE0JSON_ERROR_RECURSION6
JSON_ERROR_DEPTH1JSON_ERROR_INF_OR_NAN7
JSON_ERROR_STATE_MISMATCH2JSON_ERROR_UNSUPPORTED_TYPE8
JSON_ERROR_CTRL_CHAR3JSON_ERROR_INVALID_PROPERTY_NAME9
JSON_ERROR_SYNTAX4JSON_ERROR_UTF1610
JSON_ERROR_UTF85

Classes and interfaces

SymbolKindDescription
JsonSerializableInterfaceImplementing classes can override jsonSerialize(): mixed; json_encode() dispatches to it instead of walking public properties.
ErrorClassBase PHP error throwable with message: string, code: int, previous: ?Throwable, __construct(string $message = "", int $code = 0, ?Throwable $previous = null), and the standard Throwable methods. FiberError extends this class.
UnhandledMatchErrorClassextends Error and inherits its constructor and standard Throwable API. Explicit instances can be thrown and caught; an implicit unmatched match currently remains a fatal runtime error.
ExceptionClassBase PHP exception with message: string, code: int, previous: ?Throwable, __construct(string $message = "", int $code = 0, ?Throwable $previous = null), and the standard Throwable methods.
RuntimeExceptionClassextends Exception. Standard PHP “runtime errors” base class.
JsonExceptionClassextends RuntimeException. Carries the originating JSON_ERROR_* code; getCode() returns it (e.g. 4 = SYNTAX, 1 = DEPTH, 10 = UTF16, 7 = INF_OR_NAN).
stdClassClassDynamic-property container. $obj = new stdClass(); $obj->name = "x"; works for any property name; storage is a backing hash on the instance. json_decode($json) returns stdClass by default (PHP semantics); pass assoc: true to get an associative array.

Encoding rules for objects:

  • Classes that implement JsonSerializable dispatch to $this->jsonSerialize() and the returned value is encoded recursively.
  • Classes that do not implement JsonSerializable are encoded as a JSON object whose keys are the public properties (private and protected properties are skipped), in declaration order, including inherited public properties.

Current limitations

  • json_decode() is a full checked structural decoder: every JSON value type round-trips through a real recursive-descent parser into a boxed Mixed cell, and malformed input records the JSON error inside the decode walk instead of running a separate full-buffer validation pass first. Decode failures also record the source offset and format the PHP 8.6 location suffix (near location line:column) for json_last_error_msg() and JsonException messages without changing json_last_error() codes. nullMixed(null), true/falseMixed(bool), integers → Mixed(int), floats → Mixed(float), strings → Mixed(str) with full escape decoding (\", \\, \/, \b, \f, \n, \r, \t, \uXXXX including surrogate pairs), arrays → Mixed(array<Mixed>) with each element recursively decoded, and objects → either a stdClass instance (PHP default, assoc=false/null) or Mixed(assoc) (assoc=true). The associativity flag is threaded through _json_decode_assoc so nested objects share the caller’s choice. Container parsing uses a depth-and-string-aware boundary scanner so commas and brackets inside string values never confuse the element/pair detection. Property access ($obj->name), [] indexing ($arr["k"], $arr[0]), and count() all work directly on Mixed-typed json_decode results: codegen routes through __rt_mixed_property_get / __rt_mixed_array_get / __rt_mixed_count which unbox the cell, dispatch by runtime tag (indexed array / assoc / stdClass), and re-box typed payloads back into a Mixed cell. Missing keys, out-of-bounds indices, and unknown properties all return Mixed(null) instead of erroring, mirroring PHP’s quiet “undefined index” / “property on non-object” warnings. Use intval() / floatval() or explicit (int) / (float) / (string) casts to lift a Mixed payload back to a typed value before arithmetic since elephc’s type system requires numeric operands for + and Mixed alone does not satisfy the contract.

  • json_encode() observes JSON_UNESCAPED_SLASHES (default escapes / as \/), JSON_UNESCAPED_UNICODE (default escapes multibyte UTF-8 to lowercase-hex \uXXXXé\u00e9 — with surrogate pairs for codepoints ≥ U+10000), JSON_PRETTY_PRINT (4-space indentation, newlines between elements, single space after :), JSON_FORCE_OBJECT (indexed arrays encode as {"0":val,"1":val,...}), JSON_NUMERIC_CHECK (numeric-looking strings encode as raw JSON numbers per RFC 8259 grammar), JSON_PRESERVE_ZERO_FRACTION (integer-valued floats stay 1.0 instead of collapsing to 1), the full JSON_HEX_TAG/AMP/APOS/QUOT family (replaces </>, &, ', " with PHP’s literal uppercase-hex forms, e.g. <\u003C), Inf/NaN detection (sets JSON_ERROR_INF_OR_NAN; under JSON_THROW_ON_ERROR raises JsonException, otherwise returns false unless JSON_PARTIAL_OUTPUT_ON_ERROR is set), and malformed UTF-8 detection: every multibyte byte is validated (lead-byte range, continuation bytes, truncated sequences). Without sanitization flags this sets JSON_ERROR_UTF8 and returns false; JSON_INVALID_UTF8_IGNORE drops malformed bytes silently without raising the error code; JSON_INVALID_UTF8_SUBSTITUTE replaces malformed bytes with (or the U+FFFD UTF-8 bytes when JSON_UNESCAPED_UNICODE is also set). JSON_PARTIAL_OUTPUT_ON_ERROR keeps the partial output for errors that can be substituted.

  • JSON_THROW_ON_ERROR is observed by json_encode() for non-finite floats (Inf/NaN trigger JSON_ERROR_INF_OR_NAN), for malformed UTF-8 input (JSON_ERROR_UTF8), and by json_decode() (JSON_ERROR_SYNTAX, JSON_ERROR_DEPTH, JSON_ERROR_UTF16). Decode exceptions use the same location-aware message string as json_last_error_msg() when the failing byte offset is known. PHP does not allow this flag for json_validate(); elephc rejects it at compile time when the flag expression is static. The throw helper records the error code in _json_last_error so json_last_error() / json_last_error_msg() keep working when the flag is clear.

  • JSON_ERROR_UTF16 is set by json_decode() and json_validate() whenever a \uXXXX escape in the high-surrogate range (0xD800..0xDBFF) is not immediately followed by a low-surrogate \uYYYY (0xDC00..0xDFFF), or when a low surrogate appears without a preceding high surrogate. The detector walks the surrogate-pair handshake byte by byte, so any malformed second escape (truncated \u, non-hex digit, or out-of-range codepoint) routes to UTF16 instead of SYNTAX, matching PHP’s exact behavior.

  • The $depth argument is observed by all three JSON entry points but with the PHP-faithful split: json_encode() allows up to $depth levels of nesting (active <= limit), while json_decode() and json_validate() reject when the active nesting depth reaches $depth (active >= limit). For example, json_decode("[1]", false, 1) sets JSON_ERROR_DEPTH even though the input only nests one level deep, matching PHP. For json_encode() and json_decode(), JSON_THROW_ON_ERROR promotes the error to JsonException.

  • JSON_BIGINT_AS_STRING is observed by json_decode(). When set, integer-grammar JSON tokens (no ., no e/E) whose magnitude exceeds PHP_INT_MAX (9223372036854775807) are returned as a Mixed(string) preserving the original digits; in-range integers and any token containing ./e/E are unaffected. Detection is a length-then-lex compare against the threshold strings 9223372036854775807 (positive) / -9223372036854775808 (negative), which is safe because the fused number validator rejects RFC 8259 leading zeros — equal-length leading-zero-free decimal strings compare lexicographically the same as numerically. The flag threads through nested arrays and objects via the global _json_active_flags slot, so a bigint inside a decoded array is also returned as a string.

  • json_validate() is a recursive-descent RFC 8259 validator: it matches the literals null/true/false, validates the full number grammar (-?(0|[1-9][0-9]*)(.[0-9]+)?([eE][+-]?[0-9]+)?), checks every string escape (\", \\, \/, \b, \f, \n, \r, \t, \uHHHH with four hex digits), verifies bracket pairing in arrays/objects, requires colons between keys and values, and rejects trailing content after the value. Recursion depth is enforced against the $depth argument (default 512); on overflow it records JSON_ERROR_DEPTH. Every other malformed token records JSON_ERROR_SYNTAX.

  • catch (Throwable $e) supports dispatch to the standard Throwable method surface: getMessage(), getCode(), getFile(), getLine(), getTrace(), getTraceAsString(), getPrevious(), and __toString().

  • Associative arrays whose keys form a sequential 0..count-1 sequence in insertion order encode as JSON arrays ([...]) — matching PHP’s runtime detection. __rt_json_encode_assoc tracks that shape during the main hash walk, emits a provisional object form, and compacts the finished buffer in-place to array form only when every key matched. JSON_FORCE_OBJECT disables compaction so that flag still wins. Empty associative arrays also encode as [] (PHP’s json_encode([]) semantics).

  • Floats encode at PHP’s serialize_precision = -1 — the shortest decimal that round-trips back to the same double (so json_encode(1.0/3.0) is 0.3333333333333333, not the 14-digit echo/(string) form, and json_encode(0.1 + 0.2) is 0.30000000000000004). The JSON number layout differs from var_export: integer-valued floats drop the fraction (json_encode(100.0) is 100, not 100.0) unless JSON_PRESERVE_ZERO_FRACTION is set, and exponential magnitudes use a lowercase e with a d.d mantissa and a no-leading-zero exponent (1.0e+17, 1.0e-6). The decimal/exponential boundary matches PHP (zend_gcvt): exponential when decpt < -3 or decpt > 17. The dedicated __rt_json_ftoa runtime helper finds the shortest precision by probing snprintf("%.*e", p, x) against a strtod re-parse, independent of the default precision used elsewhere.

  • JSON helpers are emitted through the shared runtime surface on every supported target. Structural decode into Mixed, stdClass dynamic-property helpers, JsonSerializable-aware object encoding, validation, pretty-printing, depth tracking, and JSON error-message lookup are all part of that target-aware runtime path.

Serialization

FunctionSignatureNotes
serialize()serialize($value): stringProduces PHP’s serialize() wire format, byte-for-byte: N; (null), b:0;/b:1; (bool), i:<int>; (int), d:<float>; (float, shortest round-trip at serialize_precision = -1, with INF/-INF/NAN for non-finite values), s:<bytelen>:"<raw>"; (string, raw bytes with the exact byte length and no escaping), a:<count>:{<key><value>...} (indexed and associative arrays, nested, with int keys as i:K; and string keys as s:N:"...";, in insertion order), and O:<len>:"<Class>":<count>:{...} (objects).
unserialize()unserialize($data, $options = []): mixedParses the serialize() wire format back into a boxed Mixed value. Scalars, arrays, and objects round-trip exactly. Malformed or unsupported input returns false, matching PHP’s failure indicator. The $options argument is accepted for signature compatibility and currently ignored.

serialize()/unserialize() round-trip the scalar, array, and object subset exactly, and the produced bytes are interchangeable with the PHP interpreter. They share the same runtime walker family as json_encode/json_decode and reuse the shortest-float formatter, so float output matches json_encode’s precision.

Objects

Objects serialize as O:<len>:"<Class>":<count>:{...} with PHP’s exact property-key mangling: public properties use the bare name, protected use \0*\0name, and private use \0Class\0name. Properties are emitted in declaration order (inherited first).

Serialization magic methods are honoured:

  • __serialize(): array — when defined, the object body is the returned array’s key;value; pairs instead of the raw properties.
  • __unserialize(array $data): void — when defined, the parsed body is passed to it to restore the object (instead of injecting properties by name). Its $data parameter is treated as a string/int-keyed array so $data['key'] works (a bare array hint otherwise resolves to an integer-indexed array).
  • __sleep(): array — serializes only the named properties, in __sleep()’s order, using their mangled keys.
  • __wakeup(): void — runs after properties are injected (when __unserialize() is not defined).

Repeated objects within a single serialize() call are emitted as r:<index>; back-references (PHP’s global value counter: every value consumes the next index, array keys do not), and unserialize() rebuilds them as a single shared instance so === identity is preserved. This is the same machinery used to persist Phar global metadata (see Streams).

Limitations: a cyclic reference inside an object’s own properties resolves to null on unserialize() (serialization itself handles cycles correctly), and the deprecated Serializable interface (C: wire form) is not supported.

Regex

Regex functions and SPL regex iterators are documented in Regex, including the elephc native add pcre2 project setup required for their final native link.

Streams

Stream resources, standard streams, wrappers (php://, data://, phar://, http://, https://, ftp://, ftps://, compression wrappers, and glob://), stream contexts, filters, sockets, TLS, process pipes, and user wrappers are documented in Streams.

File system

FunctionSignatureDescription
file_get_contents()file_get_contents($filename, $use_include_path = false, $context = null, $offset = 0, $length = null): string|falseRead a file, or false if it cannot be opened. $offset starts the read at a byte position, counting from the end of the data when negative; a negative offset that reaches before the first byte emits file_get_contents(): Failed to seek to position N in the stream and returns false, while an offset past the end simply returns "". $length caps the bytes returned and is bounded by what is actually available; null reads to the end and a negative $length throws \ValueError before the file is opened. $use_include_path is accepted and behaves as false, because elephc resolves paths against the current directory only (the same result an include path of "." gives). $context must be null: elephc has no stream-context plumbing on the read path, so a non-null one is a compile error rather than a silently dropped option set. A literal phar:// URL is decoded at compile time; non-literal phar:// is read at runtime. Native PHAR, tar-based PHAR, and zip-based PHAR containers are readable; native gzip/bzip2 entries and ZIP deflate entries are decoded transparently. Literal and runtime-string http://, https://, ftp://, and ftps:// URLs open the matching wrapper, read the whole body, and return it (false on a failed open).
file_put_contents()file_put_contents($filename, $data): intWrite file
file()file($filename [, $flags]): array|falseRead into array of lines, or false when the file cannot be read — the only way to tell a failed read from a genuinely empty file, which returns []. $flags accepts FILE_IGNORE_NEW_LINES, FILE_SKIP_EMPTY_LINES and FILE_USE_INCLUDE_PATH (accepted, no effect). The stream-context parameter is not supported.
file_exists()file_exists($filename): boolCheck exists
is_file()is_file($filename): boolIs regular file
is_dir()is_dir($filename): boolIs directory
is_readable()is_readable($filename): boolIs readable
is_writable()is_writable($filename): boolIs writable
filesize()filesize($filename): int|falseFile size in bytes; false when the path cannot be stat’ed. An empty file is 0, not false
filemtime()filemtime($filename): int|falseModification time as a Unix timestamp; false when the path cannot be stat’ed
disk_free_space()disk_free_space($directory): float|falseFree bytes of the filesystem holding $directory; false when the path cannot be stat’ed. A full filesystem reports 0.0, which is a success
disk_total_space()disk_total_space($directory): float|falseTotal bytes of the filesystem holding $directory; false when the path cannot be stat’ed
copy()copy($source, $dest): boolCopy file
rename()rename($old, $new): boolRename/move
unlink()unlink($filename): boolDelete file
mkdir()mkdir($pathname): boolCreate directory
rmdir()rmdir($pathname): boolRemove directory
scandir()scandir($directory): arrayList files
opendir()opendir($directory): resource|falseOpen a directory stream for iteration with readdir(); returns a stream resource, or false on failure
readdir()readdir($dir_handle): string|falseRead the next entry name from a directory handle (including . and ..); returns false once every entry has been read
closedir()closedir($dir_handle): voidClose a directory handle opened by opendir()
rewinddir()rewinddir($dir_handle): voidRewind a directory handle back to its first entry
glob()glob($pattern): arrayFind matching files
getcwd()getcwd(): stringCurrent working directory
chdir()chdir($directory): boolChange directory
tempnam()tempnam($dir, $prefix): stringCreate temp filename
sys_get_temp_dir()sys_get_temp_dir(): stringSystem temp directory
FunctionSignatureDescription
symlink()symlink($target, $link): boolCreate a symbolic link at $link pointing at $target.
link()link($target, $link): boolCreate a hard link $link for an existing path $target.
readlink()readlink($path): string|falseRead the target of a symbolic link. Returns false on failure.
linkinfo()linkinfo($path): intReturns the device id (st_dev) of the link, or -1 on failure.

File metadata

FunctionSignatureDescription
fileatime()fileatime($filename): int|falseLast access time as Unix timestamp, or false on failure
filectime()filectime($filename): int|falseInode-change time as Unix timestamp, or false on failure
fileperms()fileperms($filename): int|falseFull st_mode (file-type bits + permissions), or false on failure
fileowner()fileowner($filename): int|falseOwner UID, or false on failure
filegroup()filegroup($filename): int|falseGroup GID, or false on failure
fileinode()fileinode($filename): int|falseInode number, or false on failure
filetype()filetype($filename): string|falseOne of "file", "dir", "link", "char", "block", "fifo", "socket", "unknown" for stated paths, or false on lstat() failure. Uses lstat() semantics.
is_executable()is_executable($filename): boolaccess(path, X_OK)
is_link()is_link($filename): boolTrue for symlinks (uses lstat())
is_writeable()is_writeable($filename): boolAlias of is_writable()
stat()stat($filename): array|falseAssociative array with both numeric (0..=12) and string keys (dev, ino, mode, nlink, uid, gid, rdev, size, atime, mtime, ctime, blksize, blocks), or false on failure.
lstat()lstat($filename): array|falseSame shape as stat() but does not follow symlinks, or false on failure
fstat()fstat(resource $handle): array|falseSame shape as stat() but operates on an open stream resource, or false on failure
clearstatcache()clearstatcache($clear_realpath_cache = false, $filename = ""): voidNo-op (elephc does not cache stat() results). Arguments are still evaluated.

The 13 stat() / lstat() / fstat() fields are inserted in PHP’s documented order. Check the return value against false before reading fields when the path or stream may be invalid.

On a registered userspace stream wrapper path (scheme://…), the stat family dispatches to the wrapper’s url_stat() method instead of the filesystem: stat(), lstat(), file_exists(), filesize(), filemtime(), is_file(), is_dir(), is_readable(), is_writable(), is_writeable(), and is_executable() all consult the wrapper, handing it the same STREAM_URL_STAT_* flag values PHP passes. The permission predicates follow PHP’s single-triad rule: the owner bits when the reported uid matches the process uid, the group bits when the reported gid matches the process gid or a supplementary group, the world bits otherwise. See Streams for wrapper registration.

Path manipulation

FunctionSignatureDescription
basename()basename($path [, $suffix]): stringTrailing name component. $suffix is trimmed when it is a strict suffix of the result.
dirname()dirname($path [, $levels = 1]): stringParent directory. Repeats the parent lookup when $levels is greater than 1.
pathinfo()pathinfo($path [, $flag]): array|stringWithout a flag, or with PATHINFO_ALL: associative array with keys dirname, basename, extension (when the basename contains a dot), filename. With component flags (DIRNAME, BASENAME, EXTENSION, FILENAME): the corresponding string. Runtime-computed flags are supported.
realpath()realpath($path): string|falseCanonicalized absolute path, or false when the path does not exist.
realpath_cache_get()realpath_cache_get(): arrayEmpty array; elephc does not maintain a realpath cache.
realpath_cache_size()realpath_cache_size(): int0; elephc does not maintain a realpath cache.
fnmatch()fnmatch($pattern, $filename [, $flags = 0]): boolShell-glob match. Supports *, ?, [abc], [a-z], [!abc]/[^abc], \\<char>, and PHP flags.

pathinfo() accepts PATHINFO_DIRNAME (1), PATHINFO_BASENAME (2), PATHINFO_EXTENSION (4), PATHINFO_FILENAME (8), and PATHINFO_ALL (15) constants, integer literals, variables, and bitmasks such as PATHINFO_DIRNAME | PATHINFO_EXTENSION. Component bitmasks follow PHP priority: dirname, basename, extension, then filename. The component-flag form returns the requested component as a string (or empty string when it is absent, for example pathinfo("foo", PATHINFO_EXTENSION) returns ""). The no-flag and exact PATHINFO_ALL forms return an associative array; the extension key is omitted only when the basename has no dot, matching PHP’s behaviour.

fnmatch() supports PHP’s FNM_NOESCAPE, FNM_PATHNAME, FNM_PERIOD, and FNM_CASEFOLD flags, including runtime-computed bitmasks such as FNM_PATHNAME | FNM_CASEFOLD. The numeric values are target-specific and follow the selected platform’s PHP/libc constants.

File modification

FunctionSignatureDescription
touch()touch($filename [, $mtime [, $atime]]): boolSet access/modification times. Creates the file with permissions 0666 & umask if missing. With no $mtime, or $mtime = null, uses the current time; with no $atime, or $atime = null, defaults to $mtime. On a registered scheme:// path it dispatches to the wrapper’s stream_metadata($path, STREAM_META_TOUCH, [$mtime, $atime]) with a 2-element int array.
chmod()chmod($filename, $mode): boolChange file mode. On a registered scheme:// path it dispatches to the wrapper’s stream_metadata($path, STREAM_META_ACCESS, $mode) and returns its bool result (false when the wrapper does not implement stream_metadata).
chown()chown($filename, $user): boolChange owner by UID or user name. The group is left unchanged. On a registered scheme:// path it dispatches to the wrapper’s stream_metadata($path, STREAM_META_OWNER, $uid) (integer $user) or stream_metadata($path, STREAM_META_OWNER_NAME, $name) (string $user).
chgrp()chgrp($filename, $group): boolChange group by GID or group name. The owner is left unchanged. On a registered scheme:// path it dispatches to the wrapper’s stream_metadata($path, STREAM_META_GROUP, $gid) (integer $group) or stream_metadata($path, STREAM_META_GROUP_NAME, $name) (string $group).
lchown()lchown($filename, $user): boolChange a symlink’s owner by UID or user name without following the link. The group is left unchanged.
lchgrp()lchgrp($filename, $group): boolChange a symlink’s group by GID or group name without following the link. The owner is left unchanged.
umask()umask([$mask]): intSet the process umask and return the previous value. With no argument, returns the current umask without changing it (implemented by setting umask(0) and immediately restoring the original).

Except for umask(), file-modification functions return true on success and false on failure.

touch() accepts integer Unix timestamps or null for $mtime / $atime. Numeric values, including -1, are treated as explicit timestamps; null and omitted arguments select PHP’s default/current-time behaviour.

Network utilities

FunctionSignatureDescription
gethostname()gethostname(): stringReturn the host name of the machine running the program
gethostbyname()gethostbyname(string $hostname): stringResolve a host name to its IPv4 dotted-quad address through the system resolver; returns the host name unchanged when it cannot be resolved
gethostbyaddr()gethostbyaddr(string $ip): string|falseReverse-resolve an IPv4 dotted-quad address to a host name; returns the address unchanged when no record exists, or false when it is malformed
getprotobyname()getprotobyname(string $protocol): int|falseLook up an IP protocol number by name or alias in the system protocols database; false when no entry matches
getprotobynumber()getprotobynumber(int $protocol): string|falseLook up an IP protocol name by number in the system protocols database; false when no entry matches
getservbyname()getservbyname(string $service, string $protocol): int|falseLook up an internet service port by service name or alias and protocol in the system services database; false when no entry matches
getservbyport()getservbyport(int $port, string $protocol): string|falseLook up an internet service name by port number and protocol in the system services database; false when no entry matches

Debugging

FunctionSignatureDescription
var_dump()var_dump(mixed ...$values): voidOutput the type and value of each argument in source order. Homogeneous indexed arrays of int, string, bool, or float and associative arrays (hashes) print full per-element bodies ([N]=>\n int(V)\n, ["key"]=>\n string(…)\n, etc.). Objects print object(C)#N (n) { … } with PHP’s visibility-annotated keys. An enum case prints enum(Enum::Case) at any depth, for pure and backed enums alike. Nested arrays/objects inside a Mixed-element array or hash print NULL (recursive nesting into those layouts is still pending).
print_r()print_r(mixed $value, bool $return = false): string|trueHuman-readable output. Indexed arrays, associative arrays, and arbitrarily nested arrays print PHP’s recursive Array\n(\n [key] => value\n)\n layout (unquoted keys, 4 spaces of indentation per level, 1/empty for bool true/false, empty for null). Objects print ClassName Object\n(\n [prop] => value\n)\n with PHP’s [prop:protected] / [prop:Class:private] key annotations, arbitrary array/object nesting, and PHP’s *RECURSION* marker for a revisited instance; an enum case prints Enum Enum, Enum Enum:int or Enum Enum:string with its name (and value). With $return = true, the rendering is returned instead of printed; captures are limited to 64 KiB. With $return = false, the function prints the rendering and returns true.
var_export()var_export($value, $return = false): ?stringParsable representation. Renders scalars ('…'-quoted strings with \\/\' escaping, true/false, NULL, integers, and floats — an integer-valued float gains a .0) and arbitrarily nested arrays in PHP’s array (\n key => value,\n) layout (2 spaces of indentation per level, integer keys bare and string keys quoted, nested arrays on their own line). Objects render as PHP does: stdClass as (object) array(\n 'p' => …,\n), any other class as \Class::__set_state(array(\n 'p' => …,\n)), and an enum case as \Enum::Case. Object property names are printed bare, without a visibility suffix, matching PHP. With $return = true the rendering is returned instead of printed. Floats use PHP’s serialize_precision = -1 semantics — the shortest decimal that round-trips back to the same double (so 1/3 renders as 0.3333333333333333, not the 14-digit (string) form), with the same scientific layout as PHP (1.0E+17, 1.0E-6), an integer-valued float gaining .0 (1.0, 100.0), and -0.0, INF, -INF, NAN preserved. This is independent of the default precision used by echo/(string).
<?php
$arr = [1, 2, 3];
var_dump($arr);
// array(3) {
//   [0]=> int(1)
//   [1]=> int(2)
//   [2]=> int(3)
// }

var_export(['a' => 1, 'b' => [2, 3]]);
// array (
//   'a' => 1,
//   'b' =>
//   array (
//     0 => 2,
//     1 => 3,
//   ),
// )

Output buffering

Output buffering captures everything a piece of code prints — echo, print, printf(), print_r(), var_dump(), readfile(), fpassthru() — into an in-memory buffer instead of writing it to stdout. Buffers nest: flushing an inner buffer folds its contents into the enclosing one. Buffers still active at script end (including exit()/die()) are flushed to stdout automatically, matching PHP.

FunctionSignatureDescription
ob_start()ob_start(?callable $callback = null, int $chunk_size = 0, int $flags = 112): boolStart a new output buffer (nesting supported, up to 64 levels). Output-handler callbacks are supported: closures, first-class callables, function-name strings, and boxed callables run on flush/clean with PHP’s phase bits (WRITE/CLEAN/FLUSH/FINAL, plus START on the first run); returning false passes the raw contents through, any other return is cast to a string. A non-zero $chunk_size auto-flushes the buffer whenever it reaches that many bytes, and $flags gate cleanability/flushability/removability exactly like PHP (refusals raise PHP’s notices). An unknown function name raises PHP’s warning and returns false; array-pair callables ([$obj, 'method']) are rejected at compile time
ob_get_contents()ob_get_contents(): string|falseReturn the active buffer’s contents without consuming them; false when no buffer is active
ob_get_clean()ob_get_clean(): string|falseReturn the active buffer’s contents, then discard the buffer and pop it
ob_get_flush()ob_get_flush(): string|falseReturn the active buffer’s contents, then flush them to the parent sink and pop the buffer
ob_get_length()ob_get_length(): int|falseByte length of the active buffer’s contents; false when no buffer is active
ob_get_level()ob_get_level(): intCurrent nesting depth (0 = no buffering)
ob_clean()ob_clean(): boolErase the active buffer’s contents while keeping the buffer active
ob_end_clean()ob_end_clean(): boolDiscard the active buffer’s contents and pop the buffer
ob_end_flush()ob_end_flush(): boolFlush the active buffer’s contents to the parent sink (enclosing buffer or stdout) and pop the buffer
ob_flush()ob_flush(): boolFlush the active buffer’s contents to the parent sink while keeping the buffer active
ob_get_status()ob_get_status(bool $full_status = false): arrayStatus of the active buffer (or an empty array without one): name, type, flags, level, chunk_size, buffer_size, buffer_used. With $full_status = true, one entry per nesting level
ob_implicit_flush()ob_implicit_flush(bool $enable = true): boolStores the flag and returns true. Semantically inert: elephc terminal writes are unbuffered syscalls, so implicit flushing is always effectively on
ob_list_handlers()ob_list_handlers(): arrayOne "default output handler" entry per active buffer level
<?php
ob_start();
echo "hello";
$captured = ob_get_clean();
echo strtoupper($captured);   // HELLO

ob_start();
echo "outer:";
ob_start();
echo "inner";
ob_end_flush();               // "inner" flows into the outer buffer
echo ob_get_clean(), "\n";    // outer:inner

Output buffering state is shared between statically compiled code and eval()’d code: a buffer started inside eval() captures static echoes and vice versa, and handlers registered inside eval() run on flushes triggered from static code or at script end. Output produced inside a handler is discarded and calling ob_start() from inside a handler is a fatal error, matching PHP. fwrite(STDOUT, ...) writes to the real file descriptor and bypasses output buffers, matching PHP. Known divergences: the first-class callable of a named function reports that function’s name (PHP reports Closure::__invoke), and ob_get_status() omits PHP’s internal 0x2000 disabled bit.