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.
<!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>
Default values, when passed to arguments after =, must appear at the end of the arguments list.

<!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.
A string may be used as a function name.

<!DOCTYPE html>
<html>
<head></head>
<body><?php
function shout(){
   echo "Hello World";
}
$f = "shout";
$f();
?></body>
</html>

Hello World
A function needs not exist in a place before where it is called. However, if it is defined within an if-block or within a function, it needs to be 'executed' before it becomes visible globally.

<!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
Variables inside a function are not visible outside the function. For global variables outside a function to be visible inside a function, they must first be referenced with global.

<!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
The value of a static variable in a function is preserved after the function ends. It resumes its value on the next function call.

<!DOCTYPE html>
<html>
<head></head>
<body><?php
function f(){
    static $a=0;
    echo $a;
    $a++;
}
f();f();f();
?></body>
</html>

012
To allow a function to change the value of an external variable passed to it, use a reference symbol ‘&’ in the arguments list.

<!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
To return a reference, include '&' before the function name, and include it again when assigning the function.

<!DOCTYPE html>
<html>
<head></head>
<body><?php
$a=5;
function &ref(){
   global $a;
   return $a;
}
$b =& ref();
$a=10;
echo $b;
?></body>
</html>

10
An anonymous function can be assigned to a variable, or passed in as an argument to another function. When you declare an anonymous function, remember to add the semicolon at the end of the statement.

<!DOCTYPE html>
<html>
<head></head>
<body><?php
$f = function(){echo "Hello World";};
function f2($func){
   $g=$func;
   $g();
}
f2($f);
?></body>
</html>

Hello World
Sometimes we need to use variables in the parent scope in an anonymous function in PHP (unlike a Javascript closure).

<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.
A function can take in any number of additional arguments. These arguments can be retrieved with func_num_args(), func_get_arg(i), func_get_args().

<!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>
Since PHP 5.6, a function parameter may be marked as variadic by prefixing it with .... Any extra arguments beyond the declared (non-variadic) parameters are collected into an array, giving a cleaner alternative to func_get_args():

<?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
The same ... operator also works in reverse at the call site: prefixing an array argument with ... unpacks it into individual positional arguments, one per element:

<?php
function add($a, $b, $c) {
    return $a + $b + $c;
}

$operators = [2, 3];
echo add(1, ...$operators);
?>

6
A function may enforce its arguments to be of particular types. An enforced type can be an array, a callable, a class, or an interface.

<!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 is dynamically typed, but since PHP 7.0 it has grown an increasingly expressive optional type system for function parameters, return values, and class properties. Mismatched types either get quietly coerced or throw a catchable TypeError, depending on the mode described below.

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.
<?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
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
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
Unlike a mismatched scalar, an actual return $value; inside a void function is a fatal compile-time error rather than a catchable TypeError:

<?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
A never function that returns normally anyway (instead of throwing or exiting) does throw a catchable TypeError, unlike the equivalent mistake with void:

<?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
true and false cannot both appear together in the same union – PHP considers that redundant and requires bool instead, rejected at compile time:

<?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}
Since PHP 7.3, a multi-line function call may end its argument list with a trailing comma. This keeps version-control diffs small when arguments are added or reordered later, since every existing line already ends in a comma:

<?php
function sum($a, $b, $c) {
  return $a + $b + $c;
}

echo sum(
  1,
  2,
  3,
);
?>

6
PHP 7.4's arrow functions are a compact alternative to closures: fn($x) => $x + 1 is a function whose body is a single expression that is automatically returned – there is no return keyword and no { } block, so the body cannot contain statements, only one expression.
The expression after => is implicitly returned, which makes arrow functions a natural fit for short callbacks like the one passed to array_map().
<?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 )
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).
Both forms below behave identically. Both capture $multiplier by value at definition time, so reassigning it afterwards changes neither 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
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.
Named arguments can appear in any order, and naming the parameter after an optional one skips it entirely without restating its default.
<?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)
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 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
Since PHP 8.0, the same trailing comma is allowed in a function or method's parameter list (definition), not just when calling it:

<?php
function greet(
  string $greeting,
  string $name,
) {
  return "$greeting, $name!";
}

echo greet("Hello", "World");
?>

Hello, World!
PHP 8.1's first-class callable syntaxstrlen(...) – creates a real Closure object that references a callable without invoking it. The ... is not an argument list here; it is the syntax itself, and it works for a free function, a static method, or an instance method.
All three forms produce an actual Closure instance, ready to be called, stored, or passed around like any other value.
<?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)
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 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!"
Since PHP 8.1, a parameter's default value may be a new expression – previously only constants and constant expressions were allowed there. This is called “new in initializers”, and is handy for optional dependencies such as a default “do nothing” logger that the caller can override:

<?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