PHP functions in the browser

A component runs twice: PHP renders it on the server, then the browser runs it again. Every PHP function a component calls needs a JavaScript version for the browser side, and Viewi ships one for 324 functions.

The full list, with the status of each function, is in FUNCTIONS.md in the Viewi repository. It is generated from the source and the tests, so it always matches the release.

Statuses

  • verified - the JavaScript version gives the same results as PHP in every test case, types included.
  • known difference - the same as PHP, except for a case the browser can not reproduce. See below.
  • server-only - calling it from a component fails the build.
  • internal - a helper used by other functions or by the transpiler. Calling it from a component fails the build.

The tests call the PHP function and its JavaScript version with the same arguments and compare the results. They run with the Viewi test suite (composer test).

Build errors

A function with no JavaScript version fails the build:

Function 'mb_substr' can not be found or is used outside of your source paths.

A server-only function fails the build with the reason:

file_get_contents() is server-only: there is no filesystem in a browser. Call it on the server (a service or controller) and pass the result to the component.

The server-only functions are the ones that have no meaning in a browser: the filesystem, the shell, getenv, ini_get/ini_set, set_time_limit, setcookie, function_exists and the locale functions (setlocale, strcoll and others).

Constants follow the same idea. Built-in PHP constants such as PHP_EOL or PHP_INT_MAX are replaced with their values. A constant defined with define() or const outside a class fails the build. Use a class constant instead.

Known differences

Bytes and characters

PHP string functions count bytes, JavaScript counts characters. strlen('héllo') is 6 on the server and 5 in the browser, and substr() offsets land on different characters once the text has non-ASCII characters in it.

For text that people read, use mb_strlen(), which counts characters on both sides.

Integer keys and order

A PHP array with integer keys that is not a list becomes a JavaScript object, and JavaScript objects always list integer-like keys first, in ascending order. [5 => 'x', 2 => 'y'] arrives in the browser as {"2": "y", "5": "x"}.

Keep ordered data as a list of records, not as a map keyed by ID:

// order changes in the browser
$users = [5 => 'Diana', 2 => 'Bob'];

// order stays
$users = [
    ['id' => 5, 'name' => 'Diana'],
    ['id' => 2, 'name' => 'Bob'],
];

Numbers

JavaScript has one number type, so 1 and 1.0 are the same value in the browser, and is_int() / is_float() can not tell them apart. Do not branch on int vs float in a component.

The last digit of a result from sin(), exp(), pow() and similar functions can differ between PHP and the browser. Round before comparing floats.

By-reference arguments

A by-reference array argument works: preg_match() fills $matches, sort() sorts in place.

Two cases do not work in the browser:

  • A by-reference number or string is not written back. str_replace()'s $count and similar_text()'s $percent stay empty. Compute the value another way, for example with substr_count().
  • A sort can not turn a map into a list. usort() on ['b' => 2, 'a' => 1] gives a list in PHP and an object with keys 0 and 1 in the browser. Sort a list: $sorted = array_values($map); usort($sorted, ...).

Time zone

PHP formats dates in the server's time zone, the browser in the visitor's. The same timestamp can show a different hour, or a different day, after the page loads in the browser.

Use gmdate() / gmmktime(), or format the date on the server and pass the string to the component.