The State of JS is an annual developer survey that captures how thousands of JavaScript developers feel about the language, its ecosystem, and the features they use most, from syntax and tooling to frameworks and testing.
I was reading The State of JS 2025 survey and landed on the Syntax Features section. One question caught my attention:
Which of these syntax features have you used?
It made me curious. Not just why these features are popular in JavaScript, but also:
Do we have something similar in PHP?
So I turned this into a small personal exercise:
- Understand the most-used/appreciated JavaScript syntax features
- Look for their closest equivalents in PHP
- Explain them with some examples
This article is the result of that cozy Saturday exploration.
A JavaScript to PHP walkthrough, written with curiosity rather than comparison wars.
1. Nullish Coalescing
Nullish coalescing allows you to provide a default value that is used only when a variable is truly missing (null or undefined).
JavaScript
Nullish Coalescing operator (??) provides a default value, but only when the original one is truly missing.
const someEmpty = null;
const foo = someEmpty ?? "default string";
console.log(foo); // "default string"
Here, someEmpty is null, so JavaScript falls back to 'default string'.
const zeroValue = 0;
const baz = zeroValue ?? 42;
console.log(baz); // 0
In this case, 0 is a perfectly valid value. Even though it’s falsy, it’s not null or undefined, so JavaScript keeps it.
That’s the real power of nullish coalescing: it lets you distinguish between "no value at all" and "a value that just happens to be falsy".
PHP Equivalent
PHP introduced the null coalescing operator in PHP 7.0, and it behaves almost identically.
$someEmpty = null;
$foo = $someEmpty ?? 'default string';
echo $foo; // "default string"
$zeroValue = 0;
$baz = $zeroValue ?? 42;
echo $baz; // 0
PHP behaves exactly the same way here. The null coalescing operator follows the same rules and the same mental model as JavaScript, making it immediately familiar if you’re coming from a JS background.
2. Dynamic Import
JavaScript
Dynamic imports are especially useful in JavaScript applications because the code itself has to be downloaded. By loading modules only when they are actually needed, dynamic imports help keep the initial download small, improve startup time, and reduce the amount of JavaScript shipped to the browser.
await import('/modules/my-module.js');
This makes features like code splitting and lazy loading first‑class citizens in modern frontend development.
PHP Equivalent (Conceptual)
PHP applications work very differently. The code runs on the server and does not need to be downloaded by the client, which makes dynamic imports far less critical from a performance perspective.
PHP loads files synchronously and doesn’t have a promise‑based model like JavaScript, but it still supports composing functionality at runtime:
require_once __DIR__ . '/modules/my-module.php';
In modern PHP applications, this is usually handled by autoloading. With Composer’s autoloader, classes are loaded automatically when they are referenced without requiring an explicit import via require_once:
$service = new App\Service\MyService();
While this isn’t a direct equivalent of JavaScript’s dynamic import(), PHP’s autoloading mechanism plays an important role in modern applications. It ensures that classes are loaded only when they are actually used, keeping the codebase modular and avoiding unnecessary includes, even though the underlying performance concerns differ from those in JavaScript.
3. Private Properties
JavaScript
Private properties prevents access to class internals from the outside.
class ClassWithPrivateField {
#privateField = 42;
getValue() {
return this.#privateField;
}
}
Trying to access instance.#privateField will throw an error.
PHP Equivalent
PHP has supported private properties for a long time.
class ClassWithPrivateField {
private int $privateField = 42;
public function getValue(): int {
return $this->privateField;
}
}
This gives you strong encapsulation that’s enforced at runtime. While JavaScript’s # syntax is relatively new, the idea itself will feel very familiar to PHP developers who have relied on private properties for years.
4. Logical Assignment Operators
JavaScript
Logical assignment operators assign values conditionally using logical operators.
const a = { duration: 50, title: '' };
a.duration ||= 10;
console.log(a.duration); // 50
a.title ||= 'title is empty.';
console.log(a.title); // "title is empty"
In JavaScript, the ||= operator assigns a new value only if the left-hand side is falsy. That means values like false, 0, '', null, or undefined will all trigger the assignment. It’s a convenient shorthand when you want to guarantee a usable value, but it’s important to remember that it doesn’t distinguish between “missing” values and intentionally falsy ones.
PHP Equivalent
PHP doesn’t offer a direct ||= operator, but the underlying pattern is very familiar. A common way to express it is by reassigning the value using the ternary shortcut ?:.
$a = [
'duration' => 50,
'title' => ''
];
$a['duration'] = $a['duration'] ?: 10;
echo $a['duration']; // 50
Since 50 is truthy, PHP keeps the original value.
$a['title'] = $a['title'] ?: 'title is empty.';
echo $a['title']; // "title is empty"
Here, the empty string is considered falsy (becasue is a zero length string ''), so PHP falls back to the default.
It’s worth noting that PHP’s ?: behaves like JS’s ||=, it assigns a new value when the current one is falsy, including 0, false, '', or null.
If you want behavior closer to JS’s ??=, where only truly missing values trigger the assignment, the null coalescing operator ??= is the right choice.
Since PHP 7.4 (released in 2019), PHP has had the closest equivalent to JavaScript's ??=: the null coalescing assignment operator ??=. It assigns a value only if the variable is not set or is null, leaving all other values untouched.
$a['title'] ??= 'title is empty.';
This mirrors JS's ??= behavior almost exactly and is usually the best choice when you want to provide a default only for missing values.
5. Iterator Methods
Modern JavaScript makes working with sequences of data super convenient thanks to helper methods like map, filter, and reduce, which can be applied to arrays — and, in some cases, even iterators.
JavaScript Example
const numbers = [1, 2, 3, 4, 5];
// Square the numbers, then keep only the even results, then sum them
const result = numbers
.map(x => x * x)
.filter(x => x % 2 === 0)
.reduce((sum, x) => sum + x, 0);
console.log(result); // 20 -> (4 + 16)
Here’s what happens step by step:
-
.mapsquares each number →[1, 4, 9, 16, 25] -
.filterkeeps only even numbers →[4, 16] -
.reducesums them →20
PHP Equivalent
While the fluent approach can improve readability, sometimes you can realize that the same logic can be implemented with a single loop, like this:
$result = 0;
foreach ($numbers as $number) {
if ($number % 2 !== 0) {
continue;
}
$result += $number * $number;
}
But if you want to find in PHP something similar to the previous JavaScript example, PHP has the functions for map, filter, and reduce an array:
$numbers = [1, 2, 3, 4, 5];
// Step 1: square the numbers
$squared = array_map(fn($x) => $x * $x, $numbers);
// Step 2: filter the even ones
$evenSquares = array_filter($squared, fn($x) => $x % 2 === 0);
// Step 3: sum them
$result = array_reduce($evenSquares, fn($sum, $x) => $sum + $x, 0);
echo $result; // 20
PHP 8.5 introduced the pipe operator |>, so you can chain those functions:
$numbers = [1, 2, 3, 4, 5];
$result = $numbers
|> (fn($arr) => array_map(fn($x) => $x * $x, $arr))
|> (fn($arr) => array_filter($arr, fn($x) => $x % 2 === 0))
|> (fn($arr) => array_reduce($arr, fn($sum, $x) => $sum + $x, 0));
echo $result; // 20
Same JavaScript concept, slightly different syntax:
-
array_map= JS.map -
array_filter= JS.filter -
array_reduce= JS.reduce
The pipe operator in PHP 8.5 passes only one value forward. Since array_map, array_filter, and array_reduce take multiple arguments, they need a small wrapper function to fit into the pipeline (fn($arr) =>).
If you want a fluent approach like JS, you can evaluate using one of the open-source packages (just to say that in PHP, you have a lot of options).
PHP Equivalent with the Open-Source PHP Array Package
Using the PHP Array Package library, we can achieve a similar chainable, fluent syntax in PHP.
To install the package:
composer require hi-folks/array
Once you have installed the package, you can start using it:
<?php
require "./vendor/autoload.php";
use HiFolks\DataType\Arr;
$result = Arr::make([1, 2, 3, 4, 5])
->map(fn($x) => $x * $x)
->filter(fn($x) => $x % 2 === 0)
->reduce(fn($sum, $x) => $sum + $x, 0);
echo $result; // 20 -> (4 + 16)
The PHP Array Package is available here: https://github.com/Hi-Folks/array
This package makes PHP feel almost like JavaScript here, letting you map, filter, and reduce in a single, readable chain.
6. Hashbang Grammar
JavaScript
Hashbang grammar specifies the interpreter for executable scripts.
#!/usr/bin/env node
console.log("Hello world");
This directove #!/usr/bin/env node allows JS files to be executed directly from the CLI.
To make the script executable:
chmod +x script.js
./script.js
If you want to specify bun instead of node:
#!/usr/bin/env bun
console.log("Hello world");
Hashbang grammar is technically a shell feature rather than a JavaScript-specific one. It allows a script to declare which executable should run it by specifying the interpreter (such as node) on the first line of the file.
PHP Equivalent
PHP is supported as well:
#!/usr/bin/env php
<?php
echo "Hello world";
7. error.cause
JavaScript
It preserves the original error when rethrowing.
try {
try {
throw new Error("EXCEPTION 1");
} catch (err) {
throw new Error("EXCEPTION 2", { cause: err });
}
} catch (err) {
console.log(err.message); // EXCEPTION 2
console.log(err.cause.message); // EXCEPTION 1
}
This makes debugging and error tracing much easier.
PHP Equivalent
PHP supports exception chaining natively.
try {
try {
throw new Exception("EXCEPTION 1");
} catch (Exception $err) {
throw new Exception("EXCEPTION 2", 0, $err);
}
} catch (Exception $err) {
echo $err->getMessage(); // EXCEPTION 2
echo $err->getPrevious()->getMessage(); // EXCEPTION 1
}
You can later access the original exception:
$exception->getPrevious();
This is one area where PHP has been ahead for quite some time.
Final Thoughts
This little exercise wasn’t about proving that one language is better than the other.
It was about:
- Exploring the most loved JS features by JavaScript developers (thanks to the survey)
- Appreciating how many of these ideas already exist in PHP
- Seeing how languages keep learning from each other
Most of the time, the concepts are the same, only the syntax changes.
If you enjoyed this kind of cross-language comparison, feel free to share your thoughts or suggest another feature set to explore next. Happy coding! 🚀
Top comments (2)
2. dynamic import
The main difference between JavaScript and PHP applications is that the JavaScript code needs to be downloaded, and that is where the dynamic imports help with keeping the download as small as possible.
Because PHP applications doesn't need to be downloaded the feature is less important.
5. Iterator Methods
While I like the readability of
map,filterandreducefunctions they have overhead. So when it is possible to do it in a single loop I prefer that.This isn't that much harder to understand than chaining the functions.
In PHP 8.5 the chaining can be done with the pipe operator, no need for a builder pattern.
Thanks @xwero for the feedback. Always appreciated! I’ve revised sections 2 and 5 accordingly. 🙌