PHP for Javascript Developers - Runtime
This is part 2 of a multi-part series of posts. You can read the first part, PHP for Javascript Developers - Syntax, here.
This post covers some of the runtime differences between PHP and javascript and equivalent parts of each languages standard library.
It's worth noting that PHP has an extensive standard library, far larger than common in javascript. For example, PHP has classes for many common data structures, extensive utilities for data manipulation, official extensions for database connections, file-handling, network requests, and more.
Built-in string utilities
var result = "someStr".replace("search", "replacement");
var result = "someStr".replace(/regex/, "replacement");
var matches = "someStr".match(/regex/);
var pieces = "someStr".split(",");
var length = "someStr".length;
var trimmed = " someStr \n".trim();
var boolResult = "someStr".endsWith("Str");
var boolResult = "someStr".startsWith("some");
var boolResult = "someStr".includes("eSt");
var lower = "someStr".toLowerCase();
var upper = "someStr".toUpperCase();
$result = str_replace("search", "replacement", "someStr");
$result = preg_replace("/regex/", "replacement", "someStr");
preg_match("/regex/", $matches);
$pieces = explode(",", "someStr");
$length = strlen("someStr");
// Optionally specify characters to trim.
$trimmed = trim(" someStr \n", " \t\n\r");
$boolResult = str_ends_with("someStr", "Str");
$boolResult = str_starts_with("someStr", "some");
$boolResult = str_contains("someStr", "mSt");
$lower = strtolower("someStr");
$upper = strtoupper("someStr");
Built-in numeric utilities
Number.isFinite(100);
Number.isInteger(100);
!Number.isInteger(100.52);
Number.parseInt("100");
Number.parseFloat("100.42");
is_finite(100);
is_integer(100);
is_float(100.52);
intval("100");
floatval("100.42");
Built-in array utilities
[].forEach((item, index) => {});
var strJoined = [].join(", ");
var mergedArray = arr1.concat(arr2, arr3, arr4);
var boolResult = [].includes("someVal");
[].splice(3, 20);
[].sort((a, b) => {});
var mapped = [].map((item, index) => {});
var filtered = [].filter((item, index) => {});
[].reduce((accumulator, nextValue) => {}, []);
var found = [].find((item) => {});
[].push("newItem");
[].unshift("newItemAtStart");
var lastItem = [].pop();
var firstItem = [].shift();
var reversed = [].reverse();
var someMatches = [].some((item) => {});
var allMatch = [].every((item) => {});
foreach ([] as $index => $item) {}
$strJoined = implode(", ", []);
$mergedArray = array_merge($arr1, $arr2, $arr3, $arr4);
$mergedArray = $arr4 + $arr3 + $arr2 + $arr1;
$boolResult = in_array("someVal", [], true); // 3rd param for strict equality.
$modified = array_splice($arr, 3, 20);
$sorted = usort([], function ($a, $b) {});
$mapped = array_map(function ($item) {}, []);
$filtered = array_filter([], function ($item) {});
$reduced = array_reduce([], function ($accumulator, $item) {}, []);
// No equivalent to JS [].search()
array_push($arr, "newItemAtEnd");
$arr []= "val1";
array_unshift($arr, "newItemAtStart");
$lastItem = array_pop($arr);
$firstItem = array_shift($arr);
$reversed = array_reverse($arr);
$someMatches = iterable_any([], function ($item) {}); // RFC https://wiki.php.net/rfc/any_all_on_iterable
$allMatch = iterable_some([], function ($item) {}); // RFC https://wiki.php.net/rfc/any_all_on_iterable
Built-in object utilities
var obj = {
key: "val",
};
const keys = Object.keys({});
const values = Object.values({});
const entries = Object.entries({});
const merged = Object.assign({}, obj1, obj2);
$obj = new \stdClass();
$obj->key = "val";
$arr = ['key' => 'val'];
$keys = array_keys((array) $obj); // Manges private and protected keys.
$keys = array_keys(get_object_vars($obj)); // Gets properties based on current access level.
$keys = array_keys($arr);
$values = array_values((array) $obj); // Manges private and protected keys.
$values = array_values(get_object_vars($obj)); // Gets properties based on current access level.
$values = array_values($arr);
$entries = new ArrayIterator((array) $obj); // Manges private and protected keys.
$entries = new ArrayIterator(get_object_vars($obj)); // Gets properties based on current access level.
$entries = new ArrayIterator($arr);
$merged = (object) array_merge((array) $obj, (array) $obj2); // Manges private and protected keys. Lossy (will be \stdClass at end).
$merged = (object) array_merge(get_object_vars($obj), get_object_vars($obj2)); // Gets properties based on current access level. Lossy (will be \stdClass at end).
$merged = array_merge($arr, $arr2);
Breaking Code into Multiple Files
Javascript and PHP both offer methods of splitting items into multiple files.
ES Modules
For javascript this is ES Modules.
For browser code, it is quite common to use some build-time tool to like webpack to link all the files together. Modern browsers are able to do this without a build-time tool, but this is not widely supported.
a.js
export default class A {}
export function funcA() {}
b.js
import A, { funcA } from "./a.js";
PHP require & include
The most basic form of splitting PHP code is using either require
or include
.
All of these:
- Locate the requested file.
- Parse it.
- Execute the top level code.
- If the are any symbols declared (like a function of class), these will become available for the rest of the duration of the runtime.
// Fatal error if the file does not exist.
require "./fileA.php";
// Fatal error if the file does not exist.
// Does nothing if the file has already been required.
require_once "./fileA.php";
// Warning if the file does not exist.
include "./fileA.php";
// Warning if the file does not exist.
// Does nothing if the file has already been required.
include_once "fileB.php";
There are some major downsides to manually managing includes though:
- If a try to use a symbol from a file that hasn't been included yet, it will generate a fatal error.
- You end up essentially needing to manage 1 giant list of files at the start of your program.
PHP Autoloading
Since PHP 5.1 a feature called class autoloading was added. This works only for classes and not functions and as a result PHP developers tend to use classes and methods on those classes instead of functions.
The way it works, is the language has a few places that may try to instantiate a class, check it's existance, or find out various properties about it. The language will then check for various for programmer defined functions called autoloaders. It will call each autoloader it finds, pass them the name of the requested class, and allow it to do whatever it needs to to (most commonly locating the file with that class on the filesytem, and requiring or including it).
Autoloaders are registered through a method called spl_autoload_register()
.
A PHP program can become organized like this.
spl_autoload_register(function (string $className) {
// Try to load the class from somewhere.
})
// Application will get loaded from the autoloader.
// Any classes that it uses will then get autoloaded as well.
Application::start();
Composer
Composer is a commonly used tool for PHP developers. You can think of it as a mix between NPM/yarn and webpack.
- It is a dependency manager. It fetches packages from somewhere (a registry, a github repo, the filesystem) and links them into a
/vendor
directory. - It tracks dependencies in a
composer.json
file (similar topackage.json
) and keeps a lockfilecomposer.lock
(similar to NPM'spackage-lock.json
or yarn'syarn.lock
file). - It generates an efficient class autoloader for all of the vendored dependencies.
- It allows you to specify for patterns for locating PHP files based on class names in the
composer.json
file. - It generates an autoloader for your own classes.
With composer to the structure of PHP program then becomes more like
require_once "./vendor/autoload.php";
// Application will get loaded from the autoloader.
// Any classes that it uses will then get autoloaded as well.
Application::start();
You can have multiple autoloaders in a PHP program, so it's still possible to register your own as well.
PSR-0 and PSR-4
PHP has had 2 defined standards for organizing files and autoloading them, both based on namespaces. Composer supports generating autoloaders for both of these structures.
PSR-0(now deprecated) worked with classes before the introduction of the namespace feature of the language. It's mapping worked like this.
Piece_Of_NameSpace_ClassName
->/root/Piece/Of/NameSpace/ClassName.php
PSR-4 maps to the file system according to the \
character in the namespaces.
Piece\Of\NameSpace\ClassName
->/root/Piece/Of/NameSpace/ClassName.php
Async
Unlike Javascript and some other languages, PHP does not have built-in support for powerful asyncronous primitives such as Promises, Futures, async/await etc. Instead almost all functions and methods in the PHP standard libary are blocking.
Instead PHP web servers often run muliple PHP processes at the same time. PHP's build in php-fpm module is one example.
In a web-server like node, you often run in a single event loop handling multiple requests at the same time. As a result it's a bad idea to block the entire process to wait for something like a file from the filesystem, results from the database, or network request. As a the primitives offered in javascript runtimes are often asyncronous, working heavily with callbacks and promises.
Additionally web servers in javascript (or languages like java, C#, etc) tend to start up once, and continue running for a long period of time, handling numerous requests to the same process.
PHP-FPM runs in a different model. Instead every request spins up a fresh process, with no initialized state, with multiple processes running in parallel.