PHP for Javascript Developers - Syntax
This is part 1 of a multi-part series of posts. You can read the second part, PHP for Javascript Developers - Runtime, here.
Foreword
A few years ago I started as frontend developer at Vanilla Forums knowing only frontend technologies like Javascript, Typescript, CSS, and HTML.
I've grown a lot as a developer since then, and a large part of that was becoming familiar with backend technologies like PHP & MySQL. Recently a colleague asked for a resource to be quickly get familiar with PHP. I searched around and didn't find the existing resources sufficient, so I've decided to start writing a series of posts on quickly picking up PHP from the perspective of a frontend developer.
This series assumes solid prior knowledge of javascript.
Variables
The following are equivalent.
var myStr = "test";
var myStr2 = "test";
var myBool = true;
var myInt = 1314;
var myFloat = 1.142;
// Multiple assignment
// In my view this is often confusing and considered bad practice.
var thing1, thing2;
thing1 = thing2 = 4211;
$myStr = "test";
$myStr2 = 'test';
$myBool = true;
$myInt = 1314;
$myFloat = 1.142;
// Multiple assignment
// In my view this is often confusing and considered bad practice.
$thing1 = $thing2 = 4211;
Notable differences
-
Local variables in PHP are always preceded by a
$
. -
PHP has no local variable equivalent to
const
orlet
in javascript. Every local PHP variable is scoped similar to avar
in javascript.For example they are available across blocks.
if ($someBool) {
$newVar = "foo";
}
try {
$newVar = "foo";
} catch ($err) {
$errVar = $err;
}
// Can use $newVar here sometimes.
// Although this if the if statement didn't occur this will be an error or warning (depending on PHP version).
someFunction($newVar);
someFunction($errVar);
Semicolons
Semicolons are required in PHP and optional in Javascript. If you are familiar with the places that semicolons can be placed in Javascript, you are required to place semicolons in those places in PHP.
Strings
Strings in javascript and PHP are relatively similar, at least as far as syntax goes.
They differ slighly when it comes to interpolation and concatenation though.
// Single quotes and double quotes are interchangable.
var foo = "foo";
var bar = "bar";
var obj = { key: "val" };
function getBazObj() {
return {
key: "baz",
};
}
// Concat with the + operator.
var str3 = foo + bar + obj.key; //foobarval
// Interpolation with backtick strings.
var str4 = `${foo}-${bar}-${obj.key}`; // foo-bar-val
// Any expression is allowed
var str5 = `${foo}-${getBazObj().key}`; // foo-baz
// Multiple lines allowed.
var str6 = `
Line 1
Line 2
`;
// Single quotes and double quotes are have different escaping behaviour.
// Escape sequences (like \n or \t are not interpretted).
$foo = 'foo\n';
$bar = "bar\n";
// $foo !== $bar
$obj = new \stdClass();
$obj->key = "val";
$arr = ["key" => "val"];
function getBazObj() {
return [
"key" => "baz",
];
}
// Concat with the . operator.
$str3 = $foo . $bar . $obj->key; //foobarval
// Interpolation with double quotes.
// Note the required brackets for array and object access.
$str4 = "$foo-$bar-{$obj->key}";
$str4 = "$foo-$bar-{$arr['key']}";
// ERROR, expressions are not allowed.
$str5 = "$foo-{getBazObj()['key']}";
// Multiple lines allowed in HEREDoc
// https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
$str6 = <<<SOME_PREFIX
Line 1
Line 2
SOME_PREFIX;
Arrays
Numberically indexed array syntax is equivalent between PHP and Javascript.
Arrays can be made up of multiple types, and use the [
and ]
symbols to indicate start and end.
["item1", "item2", 1, true, ["nested"]];
Older versions of PHP (PHP {"<="} 5.3) didn't support this array syntax and used array(
and )
to indicate the start and end of an array.
array("item1", "item2", 1, true, array("nested"));
Nowadays the short syntax is preferred.
Array Access
Numerically indexed array access is equivalent between PHP and javascript as well.
var items = ["item1", "item2"];
var index = 1;
items[0]; // "item1"
items[1]; // "item2"
items[index]; // "item2"
$items = ["item1", "item2"];
$index = 1;
$items[0]; // "item1"
$items[1]; // "item2"
$items[$index]; // "item2"
Objects (PHP Assosciative Arrays)
Here's our first major difference between the two languages. In PHP "array" has additional meanings than in javascript.
In Javascript you can quickly construct an object with the following syntax.
var obj = {
field1: "value",
field2: true,
};
Here's the equivalent in PHP.
$obj = [
"field1" => "value",
"field2" => true
];
// PHP <= 5.3
$obj = array(
"field1" => "value",
"field2" => true
);
The main differences are:
- Use brackets (
[]
) instead of curly braces ({}
). - Use
=>
instead of:
. - String keys must be quoted in PHP.
Differentiating assosciative and indexed arrays in PHP
While javascript has a syntax differences between quicly declaring arrays and objects, they are actually objects internally. For example
var arr = [1, 2, 3];
typeof arr === "object"; // true
// To actually check if something is an array.
Array.isArray(arr); // true.
In PHP the differentiator between the two types of arrays is purely in the keys of the array. JS equivalents will be in the comments.
// These are equivalent.
["item1", "item2"]; // ["item1", "item2"]
[0 => "item1", 1 => "item2"]; // ["item1", "item2"]
// Numerical strings are treated as numbers.
["0" => "item1", "1" => "item2"]; // ["item1", "item2"]
// Keys are out of order.
[1 => "item1", 0 => "item2"]; // {"1": "item1", "0": "item2"}
// There is a hole.
[0 => "item1", 2 => "item2"]; // {"0" :"item1", "2": "item2"}
// Has a non-numeric key.
[0 => "item1", "foo" => "item2"]; // {"0": "item1", foo: "item2"}
PHP Objects
These rules can be a bit tricky. PHP does have first class objects (instances of a class), but there is no short syntax for instantiating them currently. There is an RFC though.
// { field1: "value", field2: true }
$obj = new stdClass();
$obj->field1 = "value";
$obj->field2 = true;
Object property access
Javascript object access and PHP assosciated can be quite similar.
var obj = { field1: "value" };
var key = "field1";
obj["field1"]; // "value"
obj[key]; // "value"
// Only in javascript.
obj.field1; // "value"
$obj = [ "field1" => "value" ];
$key = "field1";
$obj["field1"]; // "value"
$obj[$key]; // "value"
Notably though these are not objects in PHP. In PHP an object is only ever instantiated from a class
with the new
keyword. Property access works a bit differently.
$obj = new stdClass();
$obj->field1 = "value";
// Property access looks the same as assignment.
$obj->field1; // "value"
Undefined and null
Javascript has both undefined
and null
as first class built in concerete types.
PHP has a built-in null
, but anything that would result in undefined
would normally be considered an notice, error or warning depending on the situation.
var obj = {
val: null,
val2: undefined,
};
var arr = [null, undefined];
obj[142]; // undefined
obj["hello"]; // undefined
obj["val"]; // null
obj.hello; // undefined
obj.val2; // undefined
obj.val; // null.
arr[0]; // null
arr[5]; // undefined.
arr[1]; // undefined.
// Check if property exists and is defined.
var exists;
exists = "thing" in obj;
exists = !!obj.thing; // false
obj.hello.other; // Error: cannot access property "other" on undefined.
Undefined property and array access in PHP is an Exception in PHP 8+.
In prior versions of PHP, such things would created a "notice" (basically a logged warning)
and evaluate to null
.
$obj = new stdClass();
// No way to set "undefined".
$obj->val = null;
$arr = [null];
$obj->{142}; // Exception
$obj->hello; // Exception
$obj->val; // null
$arr[0]; // null
$arr[5]; // Exception.
$arr["key"]; // Exception.
// Check if property exists and is defined.
$exists = property_exists($obj, "thing"); // false.
$exists = array_key_exists($obj, 0); // true
$exists = array_key_exists($obj, 5); // false
$exists = isset($obj->thing->nested->thing); // false
$exists = isset($arr['key']['nested']); // false
$exists = isset($obj->val); // true
$exists = isset($arr[0]); // true
Null Coalescing and nullsafe operator
Javascript and PHP both have a null coalescing operator and null-safe operator in recent versions.
myObj?.thing?.other?.[0] ?? "fallback";
myObj?.doThing();
// No equivalent in PHP.
myObj?.doThing?.();
// PHP 7+
$myObj->thing['other'][0] ?? "fallback";
// PHP 8+
$myObj?->doThing();
Functions
Functions declaration works looks pretty similar in PHP and Javascript.
function myFunction(property1, property2, ...extraProperties) {
// Do something.
return thing;
}
// call the function
myFunction("thing", true, "extra", "extra2");
function myFunction($property1, $property2, ...$extraProperties) {
// Do something.
return $thing;
}
myFunction("thing", true, "extra", "extra2");
Hoisting
One notable difference is that javascript has a feature called "hoisting" for functions
and variables declared with var
(but not ones declared with let
or const
).
Essentially this means you can some things before you declare them, and the javascript runtime will "hoist" or move the declaration up before the usage. There is no equivalent in PHP. you must declare things before using them.
// Totally valid
myFunction(thing);
function myFunction(arg1) {}
var thing = 2;
// Fatal Error
myFunction($thing);
function myFunction($arg1) {}
var $thing = 2;
Lambas / Anonymous Functions
PHP and javascript both have anonymous functions but the syntax is slightly different.
The following are equivalent.
var myFunc = function (arg1) {
return true;
};
myFunc("hello");
// Anonymous Callbacks
functionWithCallback(function (arg1) {
return true;
});
// Arrow functions
var myFunc2 = (arg1) => true;
var myFunc3 = (arg2) => {
return true;
};
// Anonymous Callbacks
$myFunc = function ($arg1) { return true };
$myFunc("hello");
functionWithCallback(function ($arg1) { return true });
// Arrow functions
// PHP 7.4+
var myFunc2 = fn ($arg1) => true
// INVALID SYNTAX
// Inline return expressions only are allowed.
// No return keyword.
var myFunc3 = fn ($arg2) => { return true }
Closures
One major difference in javascript and PHP is in how closures. Javascript automatically closes over any values you use in a lambda. In PHP you have to manual specify them (except for arrow functions).
const outerVariable = "foo";
const outer2 = "bar";
function myFunc() {
// Can automatically access outside variable. ("Close over" the variable automatically).
const newVar = outerVariable + outer2;
}
$outerVariable = "foo";
$outer2 = "bar";
// Can automatically access outside variable. ("Close over" the variable automatically).
$myFunc = fn () => $outerVariable . $outer2;
function myFunc() {
// ERROR. Can not access outside variable.
$newVar = $outerVariable . $outer2;
}
function myFunc() use ($outerVariable, $outer2) {
// Valid.
$newVar = $outerVariable . "bar";
}
// To modify the outside variable you need to dereference it.
function myFunc() use (&$outerVariable, $outer2) {
// Valid.
$outerVariable = $outer2;
}
Passing Variable References and by value
Another notable difference between javascript and PHP is how variable references.
I'll just be doing a brief overview, but you may come across a sticky situation in the future. If you do, please consult the PHP documentation on references.
In short this really about the ability to modify a variable after passing it to a function. In my view code is clearer when passed variables are not modified in place, but instead copied, modified, and returned.
Sometimes there can be performance (or memory) concerns with copying things, so it can be desirable to modify them in place.
Here are the javascript semantics.
function passVar(obj1, num, arr) {
obj1.prop = "laptop"; // will CHANGE original.
obj2 = { prop: "computer" }; // will NOT affect original.
num = num + 1; // will NOT affect original.
arr[0] = "hello"; // will CHANGE original.
arr = ["newArr"]; // will NOT affect original.
}
function passVar($obj, $num, $arr) {
$obj->prop = "computer"; // will CHANGE original.
$obj = new stdClass(); // will NOT affect original.
$num = num + 1; // will NOT affect original.
$arr[0] = "hello"; // will NOT affect original.
$arr['prop'] = "laptop"; // will NOT affect original.
$arr = ["newArr"]; // will NOT affect original.
}
Things may look different, but they actually are quite similar. The simple rule here, is objects can be modified after being passed. A javascript array can be modified, but as seen previously, a javscript array is an object.
The parameters can be re-assigned, but do not affect anything outside of the scope of the function.
PHP has a special operator that changes these sementics though. The derefence operator (&
).
function passVar(&$obj, &$num, &$arr) {
$obj->prop = "computer"; // will CHANGE original.
$obj = new stdClass(); // will CHANGE original.
$num = num + 1; // will CHANGE original.
$arr[0] = "hello"; // will CHANGE original.
$arr['prop'] = "laptop"; // will CHANGE original.
$arr = ["newArr"]; // will CHANGE original.
}
When using this operator, all of these examples will change the original value outside of the function.
Conditionals
Conditionals in PHP work very similarly to in javascript, even down to the automatic co-coercion of values.
The main difference is the addition of the elseif
keyword in addition to else if
.
The differences are subtle, but both are generally equivalent if you use curly braces. You can check the PHP documentation for details.
Equality
Similar to javascript, PHP has both a ==
and a ===
operator, with similar semantics.
It's recommended to use ===
to avoid WTF moments.
Ternaries
PHP and javascript both have ternary exrpressions (if/else shorthand).
var result = someConditional ? "if true" : "if false";
// Shorthand
var result = someConditional || "fallback";
// Equivalent to
var result = someConditional ? someConditional : "fallback";
$result = $someConditional ? "if true" : "if false";
// PHP specific shorthand.
$result = $someConditional ?: "fallback";
// Equivalent to
$result = $someConditional ? $someConditional : "fallback";
Switch Statements
Switch statements in javascript and PHP functional equivalently for the most part.
Loops
Basic loops are functionally identical between PHP and javascript.
for (let i = 0; i < 10; i++) {
// Do thing.
// Skip the rest of the loop.
continue;
// Break out of the loop.
break;
}
while (someCondition) {
// Do thing.
// Skip the rest of the loop.
continue;
// Break out of the loop.
break;
}
for ($i = 0; $i < 10; $i++) {
// Do thing.
// Skip the rest of the loop.
continue;
// Break out of the loop.
break;
}
while (someCondition) {
// Do thing.
// Skip the rest of the loop.
continue;
// Break out of the loop.
break;
}
Iteration
The most notable difference is in looping over iterators.
// Iterate array.
// Arrays are implicitly iterators.
for (const item of [1, 2, 3]) {
// Do thing.
}
// Iterate object.
// An iterator must be created from an object.
// Build utilties for this are `Object.entries()` `Object.keys()` and `Object.values()`.
for (const [key, value] of Object.entries(obj)) {
// Do thing.
}
// Iterate an indexed or assosciative array
foreach ([1, 2, 3] as $item) {
// Do thing
}
foreach ([1, 2, 3] as $index => $item) {
// Do thing
}
foreach (['key' => 'value'] as $index => $item) {
// Do thing
}
// Objects can be iterated only if they implement `\Iterable`.
foreach ($someIterable as $index => $value) {
// Do thing.
}
Generators & Iterables
A generator is essentially a function that returns an iterable.
function* myGenerator() {
yield "val1";
yield "val2";
yield "val3";
}
for (const item of myGenerator()) {
}
// No special syntax.
function myGenerator() {
yield "val1";
yield "val2";
yield "val3";
}
foreach (myGenerator() as $item) {
}
Classes and Properties
PHP classes work similar to classes in the latest versions of javascript.
Older Javascript
function MyClass(property) {
this.property = property;
this.method = function () {
return this.property;
};
this.otherMethod = function () {
return this.method();
};
}
MyClass.staticProperty = "static property";
MyClass.staticMethod = function () {
return "static thing";
};
MyClass.staticOtherMethod = function () {
return MyClass.staticMethod();
};
Modern Javascript
class MyClass extends OtherClass {
static staticProperty = "static property";
property;
constructor(property) {
super();
this.property = property;
}
method() {
return this.property;
}
otherMethod() {
// Call other method.
return this.method();
}
static staticMethod() {
return "static thing";
}
static staticOtherMethod() {
// Refence specific static method.
return MyClass.staticMethod();
// Reference current static method, even in subclass.
return this.staticMethod();
}
}
MyClass.staticProperty; // "static property";
MyClass.staticMethod(); // "static thing";
var instance = new MyClass("foo");
instance.property; // "foo"
instance.method(); // "foo"
PHP
class MyClass extends OtherClass {
public const CLASS_CONSTANT = "thing"; // Doesn't exist in JS.
public static $staticProperty = "static property";
private $property;
public function __construct(property) {
parent::__construct();
$this->property = $property;
}
public function method() {
return $this->property;
}
public function otherMethod() {
// Call other method.
return $this->method();
}
public static function staticMethod() {
return "static thing";
}
public static function staticOtherMethod() {
// Equivalent.
// Reference our own or parent static method.
return MyClass::staticMethod();
return self::staticMethod();
// Allows subclass to override `staticMethod()`
return static::staticMethod();
}
}
MyClass::CLASS_CONSTANT; // "thing"
MyClass::$staticProperty; // "static property";
MyClass::staticMethod(); // "static thing";
$instance = new MyClass("foo");
$instance->property; // ERROR (visibility is private).
$instance->method(); // "foo"
Notable Differences
- PHP classes support constants.
- PHP classes support visibility modifiers
- none specified -
public
is assumed. It's good practice to specify a modifier though, and be carefully consider raising internal details aboveprivate
. public
- Anything can access.protected
- The current class and subclasses can access.private
- The current class only can access. Subclasses cannot access.
- none specified -
- The
function
keyword is required. - Properties and methods are access with
->
instead of.
. - Static properties, constants, and methods are accessed with
::
.- Fun fact, you can access instance methods with
::
as well, but it can look a little weird.
- Fun fact, you can access instance methods with
$this
references the current instance instead ofthis
.self
andstatic
are used to reference static properties.self
accesses the classes own items and those of parent classes.static
accesses the classes own items and those of parent classes, and those of subclasses.
__construct
is used instead of__constructor
.- Use
parent::
instead ofsuper
Traits
PHP traits are a feature with no language equivalent in javascript. They are essentially a "piece" of a class that gets duplicated everywhere they are used.
trait MyTrait {
public function doThing() {}
}
class MyClass {
use MyTrait;
}
$instance = new MyClass();
$instance->doThing();
// ERROR - You can't instantiate a trait.
new MyTrait();
Passing references to classes, methods, and functions
Javascript has first class functions. That is in javascript a function is a type that can be passed as a variable.
PHP instead has a concept of a callable
which is a reference to some function somewhere.
function myFunction () {}
class MyClass {
method() {}
static staticMethod();
}
var lambda = function () {};
lambda();
var arrow = () => {};
arrow();
var altClass = MyClass;
new altClass();
var altFunc = myFunction;
altFunc();
var altStaticMethod = MyClass.staticMethod;
altStaticMethod();
var instance = new MyClass();
var altMethod = instance.method;
altMethod();
function myFunction () {}
class MyClass {
public function method() {}
public static function staticMethod();
}
$lambda = function () {};
$lambda();
call_user_func($lambda);
$arrow = fn () => {};
$arrow();
call_user_func($arrow);
$altClass = MyClass::class;
new $altClass();
$altClass = 'MyClass';
new $altClass();
$altFunc = 'myFunction';
$altFunc();
$altStaticMethod = [MyClass::class, 'staticMethod'];
call_user_func($altStaticMethod, ['param1', 5])
$instance = new MyClass();
$altMethod = [$instance, 'method'];
call_user_func($altStaticMethod, ['param1', 5])
Notable Differences
Passing functions and classes around in PHP genreally involves referencing something by it's string name rather than a concrete object.
Notably classes are passed by string names. Any PHP class automatically has a constant on it ::class
, that will give you the fully qualified class name. This is mainly useful when dealing with namespaced classes.
Methods are passed as an array of 2 items.
Notably there are 2 built in functions to deal with callables
.
is_callable($maybeCallable)
- Determine is something is callable.call_user_func($callable, $arguments)
- Call a callable with some arguments.
Namespaces
In PHP it is a fatal error to declare mutliple symbols with the same name, and since all symbols get loaded into the same scope (which lives for the duration of the program) you need some way to prevent conflicts.
For javascript, the solution is modules, which each have their own scope. It's not an error to have a function call doTheThing()
in multiple javascript files in one project as long as they aren't both used in the same file.
Instead of modules, PHP uses namespaces. The idea is to have some prefix on your class names to distinguish them.
In the early days of PHP this would be done with very long class names, often starting with the vendor name, and then additional pieces for what part of the program it was. For example, PHPUnit
(a major testing framework of PHP), has base test class that used to be called PHPUnit_Framework_TestCase
. Unfortunately names like this can get quite wordy, so PHP added a feature called namespaces.
So instead of this:
class PHPUnit_Framework_TestCase {}
PHPUnit_Framework_TestCase::assert();
you can do this:
namespace PHPUnit\Framework;
class TestCase {}
// Other file
use PHPUnit\Framework\TestCase;
TestCase::assert();
// Or the full name
\PHPUnit\Framework\TestCase::assert();
Using namespaces
- After declaring a
namespace
in your file, all following symbols will be considered part of that namespace. - A
use
statement brings a class into scope and allows to refer to it by a shorted name.use \PHPUnit\Framework
would allow you to callFramework\TestCase::assert()
.use \PHPUnit\Framework\TestCase
allows you to callTestCase::assert()
.- If you call a class starting with a
\
character, it will ignore anyuse
statements. - If you call a class without a
\
character:- It will check if there is a use statement for that short name.
- Then check if that symbol is the current namespace.