This tutorial comprehensively introduces the AOT compiler's Universal Methods system—a mechanism for zero-overhead method calls on native types using the $value->method() syntax.
- What Are Universal Methods
- Design Principles
- Supported Types Overview
- Int Integer Methods
- Float Floating-Point Methods
- Bool Boolean Methods
- String Methods
- Array Methods
- Stream Methods
- Big* High-Precision Type Methods
- Method Chaining
- Mutable vs Immutable Methods
- Var Type Method Lookup
- Extension Methods: Auto Discovery
- Complete Examples
Universal Methods allow you to call methods directly on variables of PHP native types, just like calling methods on objects. At compile time, the compiler translates these method calls into the corresponding C function or C++ method calls, generating zero-overhead native code.
// Call methods directly on a string
$s = "hello world";
echo $s->length(); // → strlen($s) → 11
echo $s->upper(); // → strtoupper($s) → "HELLO WORLD"
echo $s->substr(0, 5); // → substr($s, 0, 5) → "hello"
// Call methods directly on an array
$arr = [1, 3, 5, 7, 9];
echo $arr->count(); // → count($arr) → 5
echo $arr->contains(3); // → in_array(3, $arr) → true
$arr->push(11); // → array_push($arr, 11)
// Call methods directly on an integer
$a = 100;
$a->add(50); // → a += 50 → 150
echo $a->toString(); // → "150"
// Call methods directly on high-precision types
$big = std::bigInt("12345678901234567890");
echo $big->mul(2)->toString(); // → "24691357802469135780"Key idea: All universal method calls are fully resolved into direct function calls at compile time. There is no vtable lookup, reflection, or runtime type checking—the generated C++ code is exactly equivalent to hand-written C function calls.
Each universal method call goes through the following steps at compile time:
- Type inference: The compiler infers the type of the receiver (Int / Float / String / Array / Stream / BigInt / Decimal / BigFloat / Var)
- Method lookup: Look up the method definition in that type's method table
- Argument validation: Check that the number of arguments is within the
min_args~max_argsrange - Code generation: Generate the corresponding C/C++ call code based on the handler type
The universal methods system supports five internal handlers, each suited to different scenarios:
| Handler | Description | Example |
|---|---|---|
calc_op |
Directly generates C++ operator code (Int/Float only) | $a->add(1) → a + 1 |
php_fn |
Calls a PHP standard library function | $s->length() → strlen(s) |
cpp_fn |
Calls a phpx C++ static method |
$big->add(1) → BigInt::add($big, 1) |
direct_method |
Calls a Variant C++ member method | $s->equals("x") → s.equals("x") |
convert_fn |
Type conversion | $a->toFloat() → php::toFloat(a) |
php_fn_ref |
Calls a PHP function and modifies the original value by reference (Array only) | $arr->push(1) → array_push($arr, 1) |
direct_method_mutate |
Calls a mutating Variant member method | $arr->set(0, 1) → arr.set(0, 1) |
| Type | Method Count | Main Categories |
|---|---|---|
| Int | 26 | Arithmetic, math functions, type conversion |
| Float | 26 | Arithmetic, math functions, trigonometric functions, type conversion |
| Bool | 2 | Type conversion |
| String | 70+ | String operations, search, encoding, Hash, multibyte, serialization |
| Array | 50+ | CRUD, sorting, traversal, set operations, serialization |
| Stream | 30+ | Read/write, seeking, locking, Socket, filters |
| BigInt | 14 | Arithmetic, comparison, conversion, GCD |
| Decimal | 11 | Arithmetic, comparison, conversion |
| BigFloat | 10 | Arithmetic, comparison, conversion |
$a = 100;
$a->add(50); // a += 50 → 150
$a->sub(30); // a -= 30 → 70
$a->mul(2); // a *= 2 → 200
$a->div(4); // a /= 4 → 25
$a->mod(7); // a %= 7 → 2
$a->inc(); // a++ → 101
$a->dec(); // a-- → 100Mutability:
add/sub/mul/div/mod/inc/decmodify the original variable (directly operating on the C++ nativeint64_t), while also returning the new value. This differs from the immutable semantics of the Big* types.
$a = 100;
$b = $a->add(50); // $a becomes 150, $b is also 150 (returns the new value)$a = -100;
$a->abs(); // abs($a) → 100
$a->ceil(); // ceil($a) → -100.0 (float)
$a->floor(); // floor($a) → -100.0 (float)
$a->round(); // round($a) → -100.0 (float)
$a->sqrt(); // sqrt($a) → NaN (no real square root for a negative number)
$a->pow(3); // $a ** 3 → -1000000
$a->log(); // log($a) → NaN
$a->log10(); // log10($a) → NaN
$a->exp(); // exp($a) → 3.72e-44 (float)
$a->max(50); // max($a, 50) → 50
$a->min(50); // min($a, 50) → -100Note:
ceil,floor, androundreturn a Float type (standard PHP behavior).powreturns a Var type (because the result of exponentiation may overflow).
$a = 0;
$a->sin(); // sin(0) → 0.0
$a->cos(); // cos(0) → 1.0
$a->tan(); // tan(0) → 0.0
$a->asin(); // asin(0) → 0.0
$a->acos(); // acos(1) → 0.0
$a->atan(); // atan(0) → 0.0
$a->atan2(1); // atan2(0,1) → 0.0
$a->deg2rad(); // deg2rad(0) → 0.0
$a->rad2deg(); // rad2deg(0) → 0.0$a = 42;
$a->toFloat(); // (float) $a → 42.0
$a->toString(); // (string) $a → "42"
$a->toBool(); // (bool) $a → true| Method | Args | Return Type | Description |
|---|---|---|---|
add($x) |
1 | Int | Addition (modifies the original value) |
sub($x) |
1 | Int | Subtraction (modifies the original value) |
mul($x) |
1 | Int | Multiplication (modifies the original value) |
div($x) |
1 | Int | Division (modifies the original value) |
mod($x) |
1 | Int | Modulo (modifies the original value) |
inc() |
0 | Int | Increment (modifies the original value) |
dec() |
0 | Int | Decrement (modifies the original value) |
abs() |
0 | Int | Absolute value |
ceil() |
0 | Float | Round up |
floor() |
0 | Float | Round down |
round() |
0-2 | Float | Round to nearest |
sqrt() |
0 | Float | Square root |
pow($x) |
1 | Var | Exponentiation |
log() |
0-1 | Float | Natural logarithm |
log10() |
0 | Float | Base-10 logarithm |
exp() |
0 | Float | Exponential of e |
sin() |
0 | Float | Sine |
cos() |
0 | Float | Cosine |
tan() |
0 | Float | Tangent |
asin() |
0 | Float | Arcsine |
acos() |
0 | Float | Arccosine |
atan() |
0 | Float | Arctangent |
atan2($x) |
1 | Float | Two-argument arctangent |
deg2rad() |
0 | Float | Convert degrees to radians |
rad2deg() |
0 | Float | Convert radians to degrees |
max($x) |
1 | Int/Float | Maximum value |
min($x) |
1 | Int/Float | Minimum value |
toFloat() |
0 | Float | Convert to float |
toString() |
0 | String | Convert to string |
toBool() |
0 | Bool | Convert to boolean |
Float's method set is almost identical to Int's, but the return type is Float (ceil/floor/round/trigonometric functions, etc. are still Float).
$f = 3.14;
$f->add(1.0); // f += 1.0 → 4.14
$f->sub(1.0); // f -= 1.0 → 2.14
$f->mul(2.0); // f *= 2.0 → 6.28
$f->div(2.0); // f /= 2.0 → 1.57
$f->abs(); // abs(3.14) → 3.14
$f->sqrt(); // sqrt(3.14) → 1.772...
$f->sin(); // sin(3.14) → 0.00159...
$f->round(2); // round(3.14, 2) → 3.14
// Float-specific conversions
$f->toInt(); // (int) $f → 3
$f->toString(); // (string) $f → "3.14"
$f->toBool(); // (bool) $f → trueThe main differences between Float and Int methods:
- Int has
mod($x), Float does not (floating-point modulo is meaningless) - Arithmetic methods operate directly on C++
double, with performance identical to hand-written C code
The Bool type has only two conversion methods:
$b = true;
$b->toInt(); // (int) $b → 1
$b->toString(); // (string) $b → "1"String has the richest method set, covering most string operation needs in daily development.
$s = "hello world";
$s->length(); // strlen($s) → 11
$s->isEmpty(); // empty($s) → false
$s->upper(); // strtoupper($s) → "HELLO WORLD"
$s->lower(); // strtolower($s) → "hello world"
$s->upperFirst(); // ucfirst($s) → "Hello world"
$s->lowerFirst(); // lcfirst($s) → "hello world"
$s->upperWords(); // ucwords($s) → "Hello World"
$s->trim(); // trim($s) → "hello world"
$s->lTrim(); // ltrim($s) → "hello world"
$s->rTrim(); // rtrim($s) → "hello world"
$s->trim(" \t\n\r"); // trim($s, chars)$s = "hello world";
// Checking
$s->startsWith("hello"); // str_starts_with(...) → true
$s->endsWith("world"); // str_ends_with(...) → true
$s->contains("lo wo"); // str_contains(...) → true
$s->compare("hello"); // strcmp(...) → >0
$s->iCompare("HELLO"); // strcasecmp(...) → 0
$s->isNumeric(); // is_numeric(...) → false
// Position lookup
$s->indexOf("world"); // strpos($s, "world") → 6
$s->lastIndexOf("o"); // strrpos($s, "o") → 7
$s->iIndexOf("WORLD"); // stripos($s, "WORLD") → 6
$s->iLastIndexOf("O"); // strripos($s, "O") → 7
// Content lookup
$s->find("world"); // strstr($s, "world") → "world"
$s->iFind("WORLD"); // stristr($s, "WORLD") → "world"
$s->lastCharIndexOf("o"); // strrchr($s, "o") → "orld"$s = "hello world";
// Substring
$s->substr(0, 5); // substr($s, 0, 5) → "hello"
$s->substr(6); // substr($s, 6) → "world"
// Counting
$s->substrCount("l"); // substr_count($s, "l") → 3
$s->wordCount(); // str_word_count($s) → 2
// Replacement
$s->replace("hello", "hi"); // str_replace("hello", "hi", $s)
$s->iReplace("HELLO", "hi"); // str_ireplace(...)
$s->substrReplace("hi", 0, 5); // substr_replace($s, "hi", 0, 5)
$s->stripTags(); // strip_tags($s)
$s->stripTags("<br><p>"); // strip_tags($s, tags)$s = "hello world";
// Split
$words = $s->split(" "); // explode(" ", $s) → ["hello", "world"]
$words->count(); // 2
// Join (operates on an array)
$words->join(", "); // implode(", ", $words) → "hello, world"
// Repeat
$s->repeat(3); // str_repeat($s, 3) → "hello worldhello worldhello world"
// Pad
$s->pad(20, "-"); // str_pad($s, 20, "-")$s = "hello world & <test>";
$s->htmlEntityEncode(); // htmlentities($s)
$s->htmlEntityDecode(); // html_entity_decode($s)
$s->htmlSpecialCharsEncode(); // htmlspecialchars($s)
$s->htmlSpecialCharsDecode(); // htmlspecialchars_decode($s)
$s->urlEncode(); // urlencode($s) → "hello+world+%26+%3Ctest%3E"
$s->urlDecode(); // urldecode($s)
$s->rawUrlEncode(); // rawurlencode($s)
$s->rawUrlDecode(); // rawurldecode($s)
$s->addSlashes(); // addslashes($s)
$s->stripSlashes(); // stripslashes($s)
$s->addCSlashes("A..z"); // addcslashes($s, "A..z")
$s->stripCSlashes(); // stripcslashes($s)
$s->base64Encode(); // base64_encode($s)
$s->base64Decode(); // base64_decode($s)$s = "hello";
$s->md5(); // md5($s) → "5d41402abc4b2a76b9719d911017c592"
$s->sha1(); // sha1($s) → "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
$s->crc32(); // crc32($s) → 907060870
$s->hash("sha256"); // hash("sha256", $s) → "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
$s->hashCode(); // C++ std::hash → hash value (int)$s = "hello world";
// match(pattern) → returns the match array
$result = $s->match("/hello/");
// matchAll(pattern) → returns all match results
$results = $s->matchAll("/[a-z]+/");$s = '{"name":"John","age":30}';
$data = $s->jsonDecode(); // json_decode($s, true) → ["name" => "John", ...]
$obj = $s->jsonDecodeToObject(); // json_decode($s) → stdClass
$s = 'a:3:{i:0;s:3:"foo";i:1;s:3:"bar";i:2;s:3:"baz";}';
$arr = $s->unserialize(); // unserialize($s) → ["foo", "bar", "baz"]Methods with the mb prefix correspond to PHP's mb_* function family:
$s = "你好世界";
$s->mbLength(); // mb_strlen($s) → 4
$s->mbUpper(); // mb_strtoupper($s)
$s->mbLower(); // mb_strtolower($s)
$s->mbSubstr(0, 2); // mb_substr($s, 0, 2) → "你好"
$s->mbIndexOf("世界"); // mb_strpos($s, "世界") → 2
$s->mbFind("世"); // mb_strstr($s, "世")
$s->mbDetectEncoding(); // mb_detect_encoding($s)
$s->mbConvertEncoding("UTF-8"); // mb_convert_encoding($s, "UTF-8")
$s->mbConvertCase(MB_CASE_TITLE); // mb_convert_case($s, MB_CASE_TITLE)
$s->mbTrim(); // mb_trim($s)
$s->mbLTrim(); // mb_ltrim($s)
$s->mbRTrim(); // mb_rtrim($s)The following methods directly call C++ member functions of phpx::Variant, with no corresponding PHP function:
$s = "hello";
$s->equals("hello"); // C++ String.equals() — value comparison
$s->append(" world"); // C++ String.append() — append (modifies the original value)Array methods are divided into read-only methods and mutating methods. Mutating methods directly modify the original array variable.
$arr = [1, 3, 5, 7, 9];
$arr->count(); // count($arr) → 5
$arr->isEmpty(); // empty($arr) → false
$arr->isList(); // array_is_list($arr) → true$arr = [1, 2, 3];
// Mutating methods (modify the original array)
$arr->push(4); // array_push($arr, 4) → [1,2,3,4]
$arr->push(5, 6, 7); // supports multiple arguments
$arr->pop(); // array_pop($arr) → returns 7
$arr->shift(); // array_shift($arr) → returns 1
$arr->unshift(0); // array_unshift($arr, 0) → [0,...]
$arr->set(0, 100); // C++ Array.set(0, 100) → [100,...]
$arr->del(0); // C++ Array.del(0) → removes index 0
$arr->clean(); // C++ Array.clean() → []
// Read-only methods
$arr = ['a' => 1, 'b' => 2, 'c' => 3];
$arr->get('a'); // C++ Array.get('a') → 1
$arr->keyExists('a'); // array_key_exists('a', $arr) → true
$arr->contains(2); // in_array(2, $arr) → true
$arr->search(2); // array_search(2, $arr) → "b"$arr = [1, 3, 5, 7, 9];
$arr->sum(); // array_sum($arr) → 25
$arr->product(); // array_product($arr) → 945
$arr->all(fn($v) => ...); // array_all($arr, fn)
$arr->any(fn($v) => ...); // array_any($arr, fn)
$arr->map(fn($v) => $v * 2);// array_map(fn, $arr) → [2,6,10,14,18]
$arr->reduce(fn($c, $v) => $c + $v, 0); // array_reduce($arr, fn, 0)
$arr->filter(fn($v) => $v > 5); // array_filter($arr, fn)
$arr->walk(fn(&$v) => $v *= 2); // array_walk($arr, fn)$arr = [3, 1, 4, 1, 5, 9];
$arr->sort(); // sort($arr) → [1,1,3,4,5,9]
$arr->sortDesc(); // rsort($arr) → [9,5,4,3,1,1]
$arr->keySort(); // ksort($arr) → sort by key
$arr->valueSort(); // asort($arr) → sort by value (preserving keys)All sorting methods modify the original array.
$a = [1, 2, 3, 4, 5];
$b = [4, 5, 6, 7, 8];
$a->diff($b); // array_diff($a, $b) → [1,2,3]
$a->intersect($b); // array_intersect(...) → [4,5]
$a->merge($b); // array_merge($a, $b) → [1,2,3,4,5,4,5,6,7,8]
$a->unique(); // array_unique($a) → [1,2,3,4,5]
$a->flip(); // array_flip($a) → {1:0, 2:1, 3:2, ...}
$a->reverse(); // array_reverse($a) → [5,4,3,2,1]
$a->replace($b); // array_replace($a, $b)
$a->values(); // array_values($a) → re-index
$a->combine($keys); // array_combine($keys, $a)
$a->fillKeys($value); // array_fill_keys($a, $value)Methods such as
diff,intersect,merge, andreplacesupport variadic arguments:$a->merge($b, $c, $d)can merge multiple arrays at once.
$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5];
$arr->keys(); // array_keys($arr) → ['a','b','c','d','e']
$arr->slice(1, 3); // array_slice($arr, 1, 3) → ['b'=>2, 'c'=>3, 'd'=>4]
$arr->chunk(2); // array_chunk($arr, 2) → [[1,2],[3,4],[5]]
$arr->column('name'); // array_column($arr, 'name')
$arr->splice(1, 3, [6,7]); // array_splice($arr, 1, 3, [6,7]) — modifies the original array
$arr->rand(2); // array_rand($arr, 2)
$arr->keyFirst(); // array_key_first($arr) → "a"
$arr->keyLast(); // array_key_last($arr) → "e"
$arr->find(fn($v) => $v > 3); // array_find($arr, fn)$arr = ["hello", "world"];
$arr->join(", "); // implode(", ", $arr) → "hello, world"
$arr->replaceStr("hello", "hi"); // str_replace("hello", "hi", $arr)
$arr->iReplaceStr("HELLO", "hi"); // str_ireplace(...)$arr = ["name" => "John", "age" => 30];
$arr->serialize(); // serialize($arr) → "a:2:{...}"
$arr->marshal(); // serialize($arr) — alias
$arr->jsonEncode(); // json_encode($arr) → '{"name":"John","age":30}'$arr = [1, 2, 3];
$arr->toInt(); // (int) non-empty array → 1
$arr->toFloat(); // (float) non-empty array → 1.0
$arr->toBool(); // (bool) non-empty array → true
$arr->toString(); // → "Array"| Category | Methods |
|---|---|
| Basic information | count, isEmpty, isList |
| CRUD | push, pop, shift, unshift, set, get, del, clean, keyExists, contains, search |
| Traversal & aggregation | sum, product, all, any, map, reduce, filter, walk |
| Sorting | sort, sortDesc, keySort, valueSort |
| Set operations | diff, diffAssoc, diffKey, intersect, intersectAssoc, merge, unique, flip, reverse, replace, values, combine, fillKeys |
| Extraction & slicing | keys, slice, chunk, column, splice, rand, keyFirst, keyLast, find |
| String | join, replaceStr, iReplaceStr, countValues, pad |
| Serialization | serialize, marshal, jsonEncode |
| Type conversion | toInt, toFloat, toBool, toString |
The Stream type represents a file handle or network connection. Obtain it via functions such as fopen().
$fp = fopen("test.txt", "w+");
$fp->write("hello world\n"); // fwrite($fp, "hello world\n") → number of bytes written
$fp->write("more data", 4); // fwrite($fp, "more data", 4) → writes only 4 bytes
$fp->seek(0); // fseek($fp, 0) → back to the beginning
$content = $fp->read(1024); // fread($fp, 1024)
$content = $fp->getContents(); // stream_get_contents($fp)
$char = $fp->getChar(); // fgetc($fp) → reads one character
$line = $fp->getLine(); // fgets($fp) → reads one line
$line = $fp->getLine(1024); // fgets($fp, 1024)
$line = $fp->getRecord(1024, "\n"); // stream_get_line($fp, 1024, "\n")$fp->tell(); // ftell($fp) → current position
$fp->eof(); // feof($fp) → whether at EOF
$fp->stat(); // fstat($fp) → file status array
$fp->getMetaData(); // stream_get_meta_data($fp)
$fp->isLocal(); // stream_is_local($fp)
$fp->isTTY(); // stream_isatty($fp)$fp->truncate(0); // ftruncate($fp, 0) → truncates the file
$fp->sync(); // fsync($fp) → sync to disk
$fp->dataSync(); // fdatasync($fp) → sync data
$fp->close(); // fclose($fp) → close
// Locking
$fp->lock(LOCK_EX); // flock($fp, LOCK_EX)
$fp->lock(LOCK_SH); // flock($fp, LOCK_SH)
$fp->lock(LOCK_UN); // flock($fp, LOCK_UN)
// Buffer settings
$fp->setBlocking(true); // stream_set_blocking($fp, true)
$fp->setChunkSize(8192); // stream_set_chunk_size($fp, 8192)
$fp->setReadBuffer(8192); // stream_set_read_buffer($fp, 8192)
$fp->setWriteBuffer(8192); // stream_set_write_buffer($fp, 8192)
$fp->setTimeout(30); // stream_set_timeout($fp, 30)
$fp->supportsLock(); // stream_supports_lock($fp)// Server
$server = stream_socket_server("tcp://0.0.0.0:8080");
$client = $server->accept(); // stream_socket_accept($server)
$client->accept(30); // timeout of 30 seconds
// Information
$client->getSocketName(true); // stream_socket_get_name — remote address
$server->getSocketName(false); // stream_socket_get_name — local address
// Data
$client->sendTo("hello", 0, $addr); // stream_socket_sendto(...)
$client->recvFrom(1024); // stream_socket_recvfrom(...)
$client->recvFrom(1024, 0, $addr); // with address
// Control
$client->enableCrypto(true); // stream_socket_enable_crypto → enables TLS
$client->shutdown(STREAM_SHUT_RDWR); // stream_socket_shutdown(...)
// Filters
$fp->appendFilter("string.toupper"); // stream_filter_append(...)
$fp->prependFilter("string.tolower"); // stream_filter_prepend(...)$src = fopen("source.txt", "r");
$dst = fopen("dest.txt", "w");
$src->copy($dst); // stream_copy_to_stream($src, $dst)
$src->copy($dst, 4096); // specify buffer size$a = std::bigInt("12345678901234567890");
// Arithmetic (each returns a new BigInt, leaving the original unchanged)
$b = $a->add(1); // $a + 1
$c = $a->sub(1); // $a - 1
$d = $a->mul(2); // $a * 2
$e = $a->div(10); // $a / 10
$f = $a->mod(1000000); // $a % 1000000
$g = $a->pow(3); // $a ** 3
// Unary methods
$h = $a->neg(); // -$a
$i = $a->abs(); // abs($a)
// Special methods
$j = $a->gcd(15); // gcd($a, 15)
// Comparison
$cmp = $a->cmp(100); // -1/0/1
// Type conversion
$a->toString(); // → "12345678901234567890"
$a->toInt(); // → int (may truncate)
$a->toFloat(); // → float (may lose precision)| Method | Args | Return | Description |
|---|---|---|---|
add($x) |
1 | BigInt | Addition |
sub($x) |
1 | BigInt | Subtraction |
mul($x) |
1 | BigInt | Multiplication |
div($x) |
1 | BigInt | Integer division |
mod($x) |
1 | BigInt | Modulo |
pow($x) |
1 | BigInt | Exponentiation |
neg() |
0 | BigInt | Negation |
abs() |
0 | BigInt | Absolute value |
gcd($x) |
1 | BigInt | Greatest common divisor |
cmp($x) |
1 | Int | Comparison |
toString() |
0 | String | Convert to string |
toInt() |
0 | Int | Convert to integer |
toFloat() |
0 | Float | Convert to float |
$d = std::decimal("123.456");
$d->add(std::decimal("50.25")); // Addition
$d->sub(std::decimal("50.25")); // Subtraction
$d->mul(2); // Multiplication
$d->div(3); // Division
$d->mod(std::decimal("5.0")); // Modulo
$d->neg(); // Negation
$d->abs(); // Absolute value
$d->cmp(std::decimal("100")); // Comparison
$d->toString(); // → "123.456"
$d->toInt(); // → 123
$d->toFloat(); // → 123.456 (double)$bf = std::bigFloat(3.14159265);
$bf->add(1.0); // Addition
$bf->sub(1.0); // Subtraction
$bf->mul(2.0); // Multiplication
$bf->div(2.0); // Division
$bf->neg(); // Negation
$bf->abs(); // Absolute value
$bf->cmp(3.0); // Comparison → >0
$bf->toString(); // Convert to string
$bf->toInt(); // → 3
$bf->toFloat(); // → 3.14159265Immutability: Unlike Int/Float, all methods of the Big* types return new values and do not modify the original variable. See Section 12.
The return type of a universal method is known at compile time, so methods can be chained directly:
// String method chaining
$result = " Hello World! "
->trim()
->lower()
->substr(0, 5)
->upper();
echo $result; // "HELLO"
// Int method chaining
$result = 100
->add(50) // 150 (modifies the original variable)
->mul(3) // 450
->sub(100) // 350
->toString();
echo $result; // "350"
// BigInt method chaining (immutable, returns new values)
$result = std::bigInt("100")
->add(std::bigInt(50))
->mul(std::bigInt(3))
->toString();
echo $result; // "450"
// Cross-type method chaining
$sum = "123456789012345678901234567890"
->length(); // String.length() → Int
echo $sum; // 30
// Method chaining + final conversion
$result = std::bigInt("99999999999999999999")
->add(std::bigInt(1))
->toString();
echo "100000000000000000000 = " . $result;Note: Each step of an Int/Float chain modifies the original variable. If you need to keep an intermediate value, save it in a temporary variable first. Big* types do not have this problem because they are immutable.
Universal methods of different types behave differently with respect to mutability:
Int and Float arithmetic methods operate directly on C++ native variables and modify the original value:
$a = 100;
$b = $a->add(50); // This both modifies $a (becomes 150) and returns the new value to $b
echo $a; // 150 — has been modified
echo $b; // 150
$a->inc(); // $a becomes 151
$a->dec(); // $a becomes 150Array mutating methods modify the original array:
$arr = [1, 2, 3];
$arr->push(4); // modifies $arr → [1,2,3,4]
$arr->pop(); // modifies $arr → [1,2,3]
$arr->sort(); // modifies $arr → [1,2,3]
$arr->set(0, 100); // modifies $arr → [100,2,3]
$arr->clean(); // modifies $arr → []All methods of BigInt, Decimal, and BigFloat do not modify the original value and return a newly created object:
$a = std::bigInt(100);
$b = $a->add(50); // $a is still 100, $b is 150
$a = std::bigInt(100);
$a->add(50); // The return value is discarded! $a is still 100Most String methods also return new values:
$s = "hello";
$upper = $s->upper(); // $s is still "hello", $upper is "HELLO"Exception: String.append() is a mutating method (direct_method_mutate) and modifies the original string:
$s = "hello";
$s->append(" world"); // $s becomes "hello world"| Handler type | Affected types | Examples |
|---|---|---|
calc_op |
Int, Float | add, sub, mul, div, mod, inc, dec |
direct_method_mutate |
String, Array | append, set, del, clean |
php_fn_ref |
Array | push, pop, shift, unshift, sort, sortDesc, splice, walk |
When a variable's type is Var (the generic PHP type), the compiler searches the method tables in the following order:
String → Array → Int → Float → Bool → Stream → BigInt → Decimal → BigFloat
Once a matching method name is found, it generates the call code for that type. If not found, it falls back to dynamic method calls (via the ZendVM).
// The type of $x is Var (from a function return value, array extraction, etc.)
$x = some_func_returning_var();
// The compiler searches in order:
// 1. First looks for length in the String method table → found! → strlen($x)
echo $x->length();
// 2. First looks for contains in the String method table → found! → str_contains($x, ...)
echo $x->contains("test");Note: If multiple types have a method with the same name, the first matched type takes precedence. For example,
toIntexists in multiple types—the lookup stops at the first matching type.
In addition to the built-in universal methods, the compiler also supports custom extension methods. As long as a PHP function following the naming convention is defined in the current project, the compiler automatically discovers it as a universal method.
Format: {type_prefix}_{snake_case_method_name}
| Type | Prefix | Example |
|---|---|---|
| Int | int_ |
int_is_prime → $a->isPrime() |
| Float | float_ |
float_normalize → $f->normalize() |
| Bool | bool_ |
bool_toggle → $b->toggle() |
| String | str_ |
str_capitalize → $s->capitalize() |
| Array | array_ |
array_flatten → $arr->flatten() |
| Stream | stream_ |
stream_rewind → $fp->rewind() |
| BigInt | bigint_ |
bigint_is_probable_prime → $a->isProbablePrime() |
| Decimal | decimal_ |
decimal_round_to → $d->roundTo() |
| BigFloat | bigfloat_ |
bigfloat_truncate → $bf->truncate() |
<?php
declare(strict_types=1);
use native_types;
/**
* Extension method: determine whether an Int is prime
* Naming: type prefix int_ + snake_case method name is_prime
*/
function int_is_prime(int $n): bool {
if ($n < 2) return false;
for ($i = 2; $i * $i <= $n; $i++) {
if ($n % $i == 0) return false;
}
return true;
}
/**
* Extension method: flatten an array
* Naming: type prefix array_ + snake_case method name flatten
*/
function array_flatten(array $arr): array {
$result = [];
array_walk_recursive($arr, function($v) use (&$result) {
$result[] = $v;
});
return $result;
}
function main(): void {
// Auto discovery: int_is_prime → $n->isPrime()
$n = 97;
if ($n->isPrime()) {
echo "$n is prime\n";
}
// Auto discovery: array_flatten → $arr->flatten()
$nested = [[1, 2], [3, [4, 5]], 6];
$flat = $nested->flatten();
var_dump($flat); // [1, 2, 3, 4, 5, 6]
}
?>- The function's first parameter is the receiver, passed in automatically from the method call
- The return type of an extension method is fixed to
Var - Define the function before calling it—the compiler discovers functions during the analysis phase and uses them during the conversion phase
- A method's camelCase naming is automatically converted to snake_case:
isPrime→is_prime,flattenNestedArray→flatten_nested_array
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$raw = " <h1>Hello World!</h1> \n";
$processed = $raw
->trim() // strip surrounding whitespace
->stripTags() // strip HTML tags
->lower() // convert to lowercase
->upperWords(); // capitalize the first letter of each word
echo "原始: " . $raw->jsonEncode() . "\n";
echo "处理: " . $processed . "\n";
echo "长度: " . $processed->length() . "\n";
echo "是否为数字: " . (int)$processed->isNumeric() . "\n";
}
?><?php
declare(strict_types=1);
use native_types;
function main(): void {
$data = [5, 2, 8, 1, 9, 3, 7];
echo "原始: " . $data->join(", ") . "\n";
// Sorting
$data->sort();
echo "排序: " . $data->join(", ") . "\n";
// Statistics
echo "数量: " . $data->count() . "\n";
echo "求和: " . $data->sum() . "\n";
echo "最小值: " . $data->get(0) . "\n";
echo "最大值: " . $data->get($data->count() - 1) . "\n";
echo "是否包含 5: " . (int)$data->contains(5) . "\n";
// Filter and map
$even = $data->filter(function($v) { return $v % 2 == 0; });
echo "偶数: " . $even->values()->join(", ") . "\n";
$doubled = $data->map(function($v) { return $v * 2; });
echo "翻倍: " . $doubled->join(", ") . "\n";
// Serialization
echo "JSON: " . $data->jsonEncode() . "\n";
}
?><?php
declare(strict_types=1);
use native_types;
function main(): void {
// Factorial of a large integer (using compound assignment for brevity)
$n = 50;
$result = std::bigInt(1);
for ($i = 2; $i <= $n; $i++) {
$result *= $i;
}
$digits = strlen($result->toString());
echo "{$n}! has {$digits} digits\n";
// Method chaining: BigInt arithmetic + conversion
$big = std::bigInt(1000);
$val = $big->mul(3)->add(200)->sub(50)->div(10)->toString();
echo "1000 * 3 + 200 - 50 / 10 = {$val}\n";
// Decimal financial calculation
$price = std::decimal("19.99");
$qty = 5;
$taxRate = std::decimal("0.08");
$total = $price * $qty * ($taxRate->add(std::decimal(1)));
echo "总价: " . $total->toString() . "\n";
// BigFloat scientific calculation
$pi = std::bigFloat("3.14159265358979323846");
$area = $pi * 100;
echo "圆面积: " . $area->toString() . "\n";
echo "取整: " . $area->toInt() . "\n";
}
?><?php
declare(strict_types=1);
use native_types;
function main(): void {
// Write to the file
$fp = fopen("data.txt", "w+");
$fp->write("Line 1\n");
$fp->write("Line 2\n");
$fp->write("Line 3\n");
// Go back to the beginning and read
$fp->seek(0);
while (!$fp->eof()) {
$line = $fp->getLine();
if ($line !== false) {
echo $line->trim();
echo " (长度: " . $line->trim()->length() . ")\n";
}
}
$fp->close();
}
?>- High-precision type tutorial:
docs/HIGH_PRECISION_TYPES.md - Type system specification:
docs/NATIVE_TYPES.md - Universal methods implementation:
src/Parser/UniversalMethodCall.php - Integration tests:
tests/compiler/string_method/— String universal methods teststests/compiler/array_method/— Array universal methods teststests/compiler/stream_method/— Stream universal methods teststests/compiler/bigint/— BigInt universal methods teststests/compiler/decimal/— Decimal universal methods tests