MENU
Functions
A function is a piece of code which may be called using its name. It can take in arguments and return a value. Unlike variable names, function names are case-insensitive. Structuring a code with functions makes it readable and maintainable.This function takes an array as an argument and returns an array.
Default values, when passed to arguments after =, must appear at the end of the arguments list.
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function sum_product($arr){
$s=0; $p=1;
foreach ($arr as $v){
$s+=$v;
$p*=$v;
}
return [$s,$p];
}
$a=[10,20,30,40];
list($sum,$product)=sum_product($a);
echo $sum.",".$product;
?></body>
</html><!DOCTYPE html>
<html>
<head></head>
<body><?php
function shout($s,$s1="I say: ",$s2="!"){
echo $s1.$s.$s2."<br />";
}
shout ("Hello World");
shout ("Hello World","He says: ");
shout ("Hello World","She says: ",".");
?></body>
</html>I say: Hello World!
He says: Hello World!
She says: Hello World.
He says: Hello World!
She says: Hello World.
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function shout(){
echo "Hello World";
}
$f = "shout";
$f();
?></body>
</html>Hello World
<!DOCTYPE html>
<html>
<head></head>
<body><?php
shout();
shout_again(); // error without first calling shout();
function shout(){
echo "Hello World <br />";
function shout_again(){
echo "Hello World";
}
}
?></body>
</html>Hello World
Hello World
Hello World
<!DOCTYPE html>
<html>
<head></head>
<body><?php
$a=2; $b=3;
function add(){
global $a, $b;//binds
$a=&$GLOBALS["a"];
$b=&$GLOBALS["b"];
$a+=$b;
unset($a); // no effect on global $a, local $a unbound only
$c=10;
}
add();
echo $a."<br />";
echo isset($c); // false, NULL
?></body>
</html>5
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function f(){
static $a=0;
echo $a;
$a++;
}
f();f();f();
?></body>
</html>012
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function change_noref($n1,$n2){$n1++; $n2++;}
function change_ref(&$n1,&$n2){$n1++; $n2++;}
$a=5; $b=5;
change_noref($a,$b);echo ($a+$b)."<br />";
change_ref($a,$b);echo ($a+$b);
?></body>
</html>10
12
12
<!DOCTYPE html>
<html>
<head></head>
<body><?php
$a=5;
function &ref(){
global $a;
return $a;
}
$b =& ref();
$a=10;
echo $b;
?></body>
</html>10
<!DOCTYPE html>
<html>
<head></head>
<body><?php
$f = function(){echo "Hello World";};
function f2($func){
$g=$func;
$g();
}
f2($f);
?></body>
</html>Hello World
<html>
<head></head>
<body><?php
function f($s){
$s2="I say";
$f2= function () use ($s, $s2){
echo $s2." ".$s;
};
$f2();
}
f("Hello World.");
?></body>
</html>I say Hello World.
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function f($v1,$v2){
for ($i=0; $i<func_num_args(); $i++){
echo func_get_arg($i);
}
echo "<br />";
echo func_get_args()[0].func_get_args()[1].func_get_args()[2];
}
f(1,2,3,4,5,6,7);
?></body>
</html><?php
function f($req, $opt = null, ...$params) {
printf('$req: %d; $opt: %d; number of params: %d'."\n", $req, $opt, count($params));
}
f(1);
f(1, 2);
f(1, 2, 3);
f(1, 2, 3, 4);
f(1, 2, 3, 4, 5);
?>$req: 1; $opt: 0; number of params: 0
$req: 1; $opt: 2; number of params: 0
$req: 1; $opt: 2; number of params: 1
$req: 1; $opt: 2; number of params: 2
$req: 1; $opt: 2; number of params: 3
<?php
function add($a, $b, $c) {
return $a + $b + $c;
}
$operators = [2, 3];
echo add(1, ...$operators);
?>6
<!DOCTYPE html>
<html>
<head></head>
<body><?php
function f(array $v1, callable $fa, MyClass $ca){
echo $fa().$ca->v2.$v1[1];
}
$g = function(){echo "Hello World";};
class MyClass{public $v2=100;}
$c = new MyClass;
f([100,200],$g,$c);
?></body>
</html>PHP 7.0: Scalar Type Declarations and strict_types
Parameters and return values can be typed int, float, string or bool. By default PHP runs in coercive mode: a compatible value passed to a typed parameter is silently converted.In coercive mode (the default), compatible scalars are converted automatically to match the declared type.
Adding declare(strict_types=1); as the very first statement of a file switches that file to strict mode: scalar arguments must then match the declared type exactly (an int is never auto-converted from a string, though a literal int is still accepted where a float is expected). A mismatch throws a TypeError.
<?php
function describe(int $id, float $price, string $label, bool $active): string {
$priceText = number_format($price, 2);
$activeText = $active ? "yes" : "no";
return "#$id $label \$$priceText active=$activeText";
}
// Coercive mode (the default): compatible scalars are converted automatically.
echo describe("7", "9.5", 42, 1) . "\n";
?>#7 42 $9.50 active=yes
<?php
declare(strict_types=1);
function addNumbers(int $a, int $b): int {
return $a + $b;
}
try {
var_dump(addNumbers("5", "10"));
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
var_dump(addNumbers(5, 10));
?>Caught: addNumbers(): Argument #1 ($a) must be of type int, string given, called in E:\Program Files\xampp\htdocs\intro.php on line 9
int(15)
PHP 7.1: Nullable Types and the void Return Type
Prefixing a type with ?, eg. ?string, allows either that type or null. A return type of void declares that a function returns nothing meaningful; calling it still yields NULL, but the function itself must not return a value.<?php
function findName(int $id): ?string {
if ($id === 1) {
return "Alice";
}
return null;
}
var_dump(findName(1));
var_dump(findName(2));
function logMessage(string $msg): void {
echo "LOG: $msg\n";
}
var_dump(logMessage("hello"));
?>string(5) "Alice"
NULL
LOG: hello
NULL
<?php
function logMessage(string $msg): void {
return $msg;
}
logMessage("hello");
?>Fatal error: A void function must not return a value in D:\xampp\htdocs\type-declarations-void-violation.php on line 3
Stack trace:
#0 {main}
PHP 7.1: the iterable Pseudo-Type
iterable accepts either an array or an object implementing Traversable (which includes any Generator) – anything that can be used in a foreach loop. Internally, a type error against iterable reports it as Traversable|array.<?php
function sumAll(iterable $items): int {
$total = 0;
foreach ($items as $item) {
$total += $item;
}
return $total;
}
function generatorItems() {
yield 1;
yield 2;
yield 3;
}
echo sumAll([1, 2, 3]) . "\n";
echo sumAll(generatorItems()) . "\n";
try {
sumAll(5);
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>6
6
Caught: sumAll(): Argument #1 ($items) must be of type Traversable|array, int given, called in E:\Program Files\xampp\htdocs\intro.php on line 20
PHP 7.2: the object Type
object accepts an instance of any class, without restricting which one.<?php
function describe(object $obj): string {
return get_class($obj);
}
class Point {
public $x = 1;
}
echo describe(new Point()) . "\n";
echo describe((object)["a" => 1]) . "\n";
try {
describe("not an object");
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>Point
stdClass
Caught: describe(): Argument #1 ($obj) must be of type object, string given, called in E:\Program Files\xampp\htdocs\intro.php on line 14
PHP 7.4: Typed Properties
Class properties may declare a type just like parameters. Assigning an incompatible value throws a TypeError, and reading a typed property that was never given a value throws an Error for being uninitialized (this is different from being null).<?php
class Product {
public int $id;
public string $name = "Untitled";
}
$p = new Product();
$p->id = 42;
echo $p->id . " " . $p->name . "\n";
try {
$p->id = "not a number";
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
$q = new Product();
try {
echo $q->id;
} catch (\Error $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>42 Untitled
Caught: Cannot assign string to property Product::$id of type int
Caught: Typed property Product::$id must not be accessed before initialization
PHP 8.0: Union Types and the mixed Type
A union type, eg. int|string, accepts any one of the listed types. mixed is shorthand for “any type at all” (including null) and needs no ? prefix.<?php
function formatId(int|string $id): string {
return "ID-" . $id;
}
echo formatId(42) . "\n";
echo formatId("A100") . "\n";
try {
formatId([1, 2, 3]);
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
function describeAnything(mixed $value): string {
return gettype($value);
}
echo describeAnything(42) . "\n";
echo describeAnything("text") . "\n";
echo describeAnything(null) . "\n";
echo describeAnything([1, 2]) . "\n";
?>ID-42
ID-A100
Caught: formatId(): Argument #1 ($id) must be of type string|int, array given, called in E:\Program Files\xampp\htdocs\intro.php on line 10
integer
string
NULL
array
PHP 8.1: the never Return Type and Pure Intersection Types
never promises that a function will not return control to the caller at all – it always throws, exits, or loops forever. An intersection type, eg. Countable&ArrayAccess, requires an object that implements every listed interface at once (unlike a union, which only needs one).<?php
function fail(string $message): never {
throw new \RuntimeException($message);
}
try {
fail("Something went wrong");
} catch (\RuntimeException $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
class Collection implements \Countable, \ArrayAccess {
private array $items = [];
public function count(): int { return count($this->items); }
public function offsetExists($offset): bool { return isset($this->items[$offset]); }
public function offsetGet($offset): mixed { return $this->items[$offset]; }
public function offsetSet($offset, $value): void {
if ($offset === null) { $this->items[] = $value; }
else { $this->items[$offset] = $value; }
}
public function offsetUnset($offset): void { unset($this->items[$offset]); }
}
function reportSize(\Countable&\ArrayAccess $collection): int {
return count($collection);
}
$c = new Collection();
$c[] = "a";
$c[] = "b";
echo reportSize($c) . "\n";
class CountOnly implements \Countable {
public function count(): int { return 0; }
}
try {
reportSize(new CountOnly());
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>Caught: Something went wrong
2
Caught: reportSize(): Argument #1 ($collection) must be of type Countable&ArrayAccess, CountOnly given, called in E:\Program Files\xampp\htdocs\intro.php on line 38
<?php
function fail(string $message): never {
echo "About to return normally...\n";
}
try {
fail("test");
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>About to return normally...
Caught: fail(): never-returning function must not implicitly return
PHP 8.2: Disjunctive Normal Form (DNF) Types
DNF types let intersection groups be combined inside a union, eg. (A&B)|C: each intersection group must be parenthesized.<?php
interface Talkable {
public function talk(): string;
}
interface Walkable {
public function walk(): string;
}
class Robot implements Talkable, Walkable {
public function talk(): string { return "beep"; }
public function walk(): string { return "clank"; }
}
class Vehicle {
public function honk(): string { return "beep beep"; }
}
function operate((Talkable&Walkable)|Vehicle $thing): string {
if ($thing instanceof Vehicle) {
return $thing->honk();
}
return $thing->talk() . " " . $thing->walk();
}
echo operate(new Robot()) . "\n";
echo operate(new Vehicle()) . "\n";
try {
operate(new \stdClass());
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>beep clank
beep beep
Caught: operate(): Argument #1 ($thing) must be of type (Talkable&Walkable)|Vehicle, stdClass given, called in E:\Program Files\xampp\htdocs\intro.php on line 29
PHP 8.2: Standalone null, false and true Types
Before 8.2, null and false were only usable inside a union (eg. ?string, or string|false as strpos() once returned). PHP 8.2 allows null, false and true to stand alone as a complete type, which is mostly useful for documenting a function that always returns the same constant value.<?php
function alwaysTrue(): true {
return true;
}
function alwaysFalse(): false {
return false;
}
function alwaysNull(): null {
return null;
}
var_dump(alwaysTrue());
var_dump(alwaysFalse());
var_dump(alwaysNull());
function broken(): false {
return true;
}
try {
broken();
} catch (\TypeError $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>bool(true)
bool(false)
NULL
Caught: broken(): Return value must be of type false, true returned
<?php
function isEven(int $n): true|false {
return $n % 2 === 0;
}
?>Fatal error: Type contains both true and false, bool must be used instead in D:\xampp\htdocs\type-declarations-bool-redundant.php on line 2
Stack trace:
#0 {main}
<?php
function sum($a, $b, $c) {
return $a + $b + $c;
}
echo sum(
1,
2,
3,
);
?>6
The expression after => is implicitly returned, which makes arrow functions a natural fit for short callbacks like the one passed to array_map().
The other key difference from a traditional closure is variable capture. An arrow function automatically captures every variable it references from the enclosing scope, by value, with no extra syntax. A traditional function () { ... } closure captures nothing automatically – each outer variable it needs must be listed explicitly with use ($var).
<?php
declare(strict_types=1);
$add1 = fn(int $x): int => $x + 1;
echo $add1(4) . "\n";
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn(int $n): int => $n * 2, $numbers);
print_r($doubled);
?>5
Array
(
[0] => 2
[1] => 4
[2] => 6
[3] => 8
[4] => 10
)
Both forms below behave identically. Both capture $multiplier by value at definition time, so reassigning it afterwards changes neither one.
Since PHP 8.0, named arguments let you pass a value by writing the parameter's name before it, as in f(name: $value). This makes calls self-documenting and order-independent, and lets you skip an optional parameter instead of repeating its default value just to reach a later one.
<?php
declare(strict_types=1);
$multiplier = 3;
// Arrow function: automatically captures $multiplier by value from the outer scope.
$arrowFn = fn(int $x): int => $x * $multiplier;
// Traditional closure: must explicitly capture it with 'use'.
$closureFn = function (int $x) use ($multiplier): int {
return $x * $multiplier;
};
echo $arrowFn(10) . "\n";
echo $closureFn(10) . "\n";
// Both capture by VALUE at definition time: changing $multiplier afterwards does not affect them.
$multiplier = 100;
echo $arrowFn(10) . "\n";
echo $closureFn(10) . "\n";
?>30
30
30
30
Named arguments can appear in any order, and naming the parameter after an optional one skips it entirely without restating its default.
Positional arguments must always come before named ones in the same call, but the two mix freely – positional arguments fill parameters left to right, and named ones fill whatever remains, as country: does above while leaving $age at its default. Named arguments also interact with variadic parameters (...$rest): a named argument that doesn't match any declared parameter is collected into the variadic array, keyed by its name instead of by position.
<?php
declare(strict_types=1);
function makeUser(string $name, int $age = 18, string $country = "USA"): string {
return "$name ($age, $country)";
}
// Named arguments can be passed in any order.
echo makeUser(name: "Alice", country: "UK", age: 30) . "\n";
// Skip an optional parameter by naming the one after it.
echo makeUser(name: "Bob", country: "Canada") . "\n";
// Combine positional and named arguments (positional must come first).
echo makeUser("Charlie", country: "Australia") . "\n";
?>Alice (30, UK)
Bob (18, Canada)
Charlie (18, Australia)
<?php
declare(strict_types=1);
function buildQuery(string $table, ...$conditions): string {
var_dump($conditions);
$parts = [];
foreach ($conditions as $key => $value) {
$parts[] = "$key=$value";
}
return "SELECT * FROM $table WHERE " . implode(" AND ", $parts);
}
echo buildQuery(table: "users", status: "active", role: "admin") . "\n";
?>array(2) {
["status"]=>
string(6) "active"
["role"]=>
string(5) "admin"
}
SELECT * FROM users WHERE status=active AND role=admin
<?php
function greet(
string $greeting,
string $name,
) {
return "$greeting, $name!";
}
echo greet("Hello", "World");
?>Hello, World!
All three forms produce an actual Closure instance, ready to be called, stored, or passed around like any other value.
Before PHP 8.1, the only ways to obtain a Closure from an existing function or method were a string name ('strlen'), an array pair ([$obj, 'method']), or an explicit call to Closure::fromCallable() – none of which an IDE or static analyzer can verify actually exist or match the expected signature. The first-class callable syntax is the equivalent one-liner that can be checked.
<?php
declare(strict_types=1);
class MathHelper {
public static function square(int $n): int {
return $n * $n;
}
public function cube(int $n): int {
return $n ** 3;
}
}
// Free function.
$strlenFn = strlen(...);
var_dump($strlenFn("hello"));
// Static method.
$squareFn = MathHelper::square(...);
var_dump($squareFn(5));
// Instance method.
$helper = new MathHelper();
$cubeFn = $helper->cube(...);
var_dump($cubeFn(3));
// Each produces a real Closure instance, unlike a plain string or array callable.
var_dump($strlenFn instanceof \Closure);
?>int(5)
int(25)
int(27)
bool(true)
<?php
declare(strict_types=1);
class Greeter {
public function greet(string $name): string {
return "Hello, $name!";
}
}
$greeter = new Greeter();
// Old ways to obtain a Closure: a string name, an array [$obj, 'method'], or Closure::fromCallable().
$oldFree = Closure::fromCallable('strlen');
$oldMethod = Closure::fromCallable([$greeter, 'greet']);
var_dump($oldFree('hello'));
var_dump($oldMethod('World'));
// PHP 8.1's first-class callable syntax is the concise, statically-analyzable equivalent.
$newFree = strlen(...);
$newMethod = $greeter->greet(...);
var_dump($newFree('hello'));
var_dump($newMethod('World'));
?>int(5)
string(13) "Hello, World!"
int(5)
string(13) "Hello, World!"
<?php
interface Logger {
public function log(string $message): void;
}
class NullLogger implements Logger {
public function log(string $message): void {
// intentionally does nothing
}
}
class FileLogger implements Logger {
public function log(string $message): void {
echo "LOG: $message\n";
}
}
function process(string $data, Logger $logger = new NullLogger()) {
$logger->log("Processing: $data");
return strtoupper($data);
}
echo process("hello") . "\n";
echo process("world", new FileLogger()) . "\n";
?>HELLO
LOG: Processing: world
WORLD