Функции, определяемые пользователем

Псевдокод для демонстрации использования функций:

function foo($arg_1, $arg_2, /* ..., */ $arg_n) {
	echo "Пример функции";
	
	return $retval;
}

Внутри функции можно использовать любой корректный PHP-код, в том числе другие функции и даже объявления классов.

Имена функций следуют тем же правилам, что и другие метки в PHP. Корректное имя функции начинается с буквы или знака подчеркивания, за которым следует любое количество букв, цифр или знаков подчеркивания. В качестве регулярного выражения оно может быть выражено так: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$.

Функции не обязаны быть определены до их использования, исключая тот случай, когда функции определяются условно. В случае, когда функция определяется в зависимости от какого-либо условия, обработка описания функции должна предшествовать ее вызову:

$makefoo = true;

/*
	Мы не можем вызвать функцию foo() в этом месте,
	поскольку она еще не определена, но мы можем обратиться к bar()
*/

bar();

if ($makefoo) {
	function foo() {
		echo "Я не существую до тех пор, пока выполнение программы меня не достигнет.";
	}
}

/*
	Теперь мы благополучно можем вызывать foo(),
	поскольку $makefoo была интерпретирована как true
*/

if ($makefoo) foo();

function bar() {
	echo "Я существую сразу с начала старта программы.";
}
$makefoo = true;

foo();

if ($makefoo) {
	function foo() {
		echo "Я не существую до тех пор, пока выполнение программы меня не достигнет.";
	}
}

// Fatal error: Uncaught Error: Call to undefined function foo()

Вложенные функции:

function foo()  {
	function bar() {
		echo "Я не существую пока не будет вызвана foo()";
	}
}

/*
	Мы пока не можем обратиться к bar(),
	поскольку она еще не определена.
*/

foo();

/*
	Теперь мы можем вызвать функцию bar(),
	обработка foo() сделала ее доступной.
*/

bar();

Все функции и классы PHP имеют глобальную область видимости - они могут быть вызваны вне функции, даже если были определены внутри и наоборот.

PHP не поддерживает перегрузку функции, также отсутствует возможность переопределить или удалить объявленную ранее функцию.

Функции PHP поддерживают как списки аргументов переменной длины, так и значения аргументов по умолчанию.

Можно вызывать функции PHP рекурсивно:

function recursion($a) {
    if ($a < 20) {
        echo "$a";

        recursion($a + 1);
    }
}

recursion(1);

Рекурсивный вызов методов с глубиной более 100-200 уровней рекурсии может вызвать переполнение стека и привести к аварийному завершению скрипта. В частности, бесконечная рекурсия будет считаться программной ошибкой.


Аргументы функции

Функция может принимать информацию в виде списка аргументов, который является списком разделенных запятыми выражений. Аргументы вычисляются слева направо.

PHP поддерживает передачу аргументов по значению (по умолчанию), передачу аргументов по ссылке, и значения по умолчанию.

Передача массива в функцию:

function takes_array($input) {
    echo "$input[0] + $input[1] = ", $input[0] + $input[1];
}

takes_array([2, 3]);

Передача аргументов по ссылке

По умолчанию аргументы в функцию передаются по значению (это означает, что если вы измените значение аргумента внутри функции, то вне ее значение все равно останется прежним). Если вы хотите разрешить функции модифицировать свои аргументы, вы должны передавать их по ссылке.

Если вы хотите, чтобы аргумент всегда передавался по ссылке, вы можете указать амперсанд (&) перед именем аргумента в описании функции:

// Example 1
function test1($string) {
    $string = 'Test 1 inside function.';
}

$str = 'Test 1 outside function.';

test1($str);

echo $str; // Test 1 outside function.

// Example 2
function test2(&$string) {
    $string = 'Test 2 inside function.';
}

$str = 'Test 2 outside function.';

test2($str);

echo $str; // Test 2 inside function.

// Example 3
function test3(&$string) {
    $string .= 'Test 3 inside function.';
}

$str = 'Test 3 outside function.';

test3($str);

echo $str; // Test 3 outside function. Test 3 inside function.

Значения аргументов по умолчанию

Функция может определять значения по умолчанию в стиле C++ для скалярных аргументов, например:

function makecoffee($type = "капучино") {
    return "Готовим чашку $type";
}

echo makecoffee(); // Готовим чашку капучино
echo makecoffee(null); // Готовим чашку
echo makecoffee("эспрессо"); // Готовим чашку эспрессо

PHP также позволяет использовать массивы (array) и специальный тип NULL в качестве значений по умолчанию, например:

function makecoffee($types = array("капучино"), $coffeeMaker = NULL) {
    $device = is_null($coffeeMaker) ? "вручную" : $coffeeMaker;
    
	return "Готовлю чашку ".join(", ", $types)." $device";
}

echo makecoffee();
// Готовлю чашку капучино вручную

echo makecoffee(array("капучино", "лавацца"), "в чайнике");
// Готовлю чашку капучино, лавацца в чайнике

Значение по умолчанию должно быть константным выражением, а не (к примеру) переменной или вызовом функции/метода класса.

Обратите внимание, что все аргументы, для которых установлены значения по умолчанию, должны находиться правее аргументов, для которых значения по умолчанию не заданы, в противном случае ваш код может работать не так, как вы этого ожидаете. Рассмотрим следующий пример:

function makeyogurt($type = "ацидофил", $flavour) {
    return "Готовим чашку из бактерий $type со вкусом $flavour";
}
 
echo makeyogurt("малины");
// Fatal error: Uncaught ArgumentCountError: Too few arguments to function makeyogurt()

Теперь сравним его со следующим примером:

function makeyogurt($flavour, $type = "ацидофил") {
    return "Готовим чашку из бактерий $type со вкусом $flavour";
}
 
echo makeyogurt("малины");
// Готовим чашку из бактерий ацидофил со вкусом малины

Объявление типов

Объявления типов позволяют функциям строго задавать тип передаваемых параметров. Передача в функцию значений несоответствующего типа будет приводить к ошибке: в PHP 5 это будет обрабатываемая фатальная ошибка, а в PHP 7 будет выбрасываться исключение TypeError.

Чтобы объявить тип аргумента, необходимо перед его именем добавить имя требуемого типа. Объявление типов может принимать значения NULL, если значение по умолчанию для аргумента является NULL.

Допустимые типы:

  • Имя класса/интерфейса - Аргумент должен быть instanceof, что и имя класса или интерфейса;
  • self - Этот параметр должен быть instanceof того же класса, в методе которого он указан. Это можно использовать только в методах класса или экземпляра;
  • array - Аргумент должен быть типа array;
  • callable - Аргумент должен быть корректным callable-типом;
  • bool - Аргумент должен быть типа boolean;
  • float - Аргумент должен быть типа float;
  • int - Аргумент должен быть типа integer;
  • string - Аргумент должен иметь тип string;
  • iterable - Параметр должен быть либо массивом, либо экземпляром класса, реализующего Traversable;
  • object - Параметр должен быть объектом (object);

Псевдонимы для вышеперечисленных скалярных типов не поддерживаются. Вместо этого они рассматриваются как имена классов или интерфейсов. К примеру, используя boolean как параметр или возвращаемое значение, потребует, чтобы эти аргумент или возвращаемое значение были instanceof класса или интерфейса boolean, а не типа bool.

class C {}
class D extends C {}

// Это не является расширением класса C.
class E {}

function f(C $c) {
    echo get_class($c);
}

f(new C); // C
f(new D); // D
f(new E); // Fatal error: Uncaught TypeError: Argument 1 passed to f() must be an instance of C, instance of E given
interface I { public function f(); }
class C implements I { public function f() {} }

// Это не реализует интерфейс I.
class E {}

function f(I $i) {
    echo get_class($i);
}

f(new C); // C
f(new E); // Fatal error: Uncaught TypeError: Argument 1 passed to f() must implement interface I, instance of E given

Строгая типизация

По умолчанию PHP будет пытаться привести значения несоответствующих типов к скалярному типу, если это возможно. Например, если в функцию передается целое число (integer), а тип аргумента объявлен как строка (string), в итоге функция получит преобразованное в строку (string) значение.

Для отдельных файлов можно включать режим строгой типизации. В этом режиме в функцию можно передавать значения только тех типов, которые объявлены для аргументов. В противном случае будет выбрасываться исключение TypeError. Есть лишь одно исключение - целое число (integer) можно передать в функцию, которая ожидает значение типа float. Вызовы функций внутри встроенных функций не будут затронуты директивой strict_types.

Для включения режима строгой типизации используется выражение declare в объявлении strict_types:

declare(strict_types=1);

function sum(int $a, int $b) {
    return $a + $b;
}

var_dump(sum(1, 2)); // int(3)
var_dump(sum(1.5, 2.5)); // Fatal error: Uncaught TypeError: Argument 1 passed to sum() must be of the type int, float given
function sum(int $a, int $b) {
    return $a + $b;
}

var_dump(sum(1, 2)); // int(3)
var_dump(sum(1.5, 2.5)); // int(3)

Списки аргументов переменной длины

PHP поддерживает списки аргументов переменной длины для функций, определяемых пользователем. Для версий PHP 5.6 и выше это делается добавлением многоточия (...). Для версий 5.5 и старше используются функции func_num_args(), func_get_arg() и func_get_args().

В версиях PHP 5.6 и выше список аргументов может содержать многоточие ..., чтобы показать, что функция принимает переменное количество аргументов. Аргументы в этом случае будут переданы в виде массива. Например:

function sum(...$numbers) {
    $acc = 0;
	
    foreach ($numbers as $n) {
        $acc += $n;
    }
	
    return $acc;
}

echo sum(1, 2, 3, 4); // 10

Многоточие (...) можно использовать при вызове функции, чтобы распаковать массив (array) или Traversable переменную в список аргументов:

function add($a, $b) {
    return $a + $b;
}

echo add(...[1, 2]); // 3

$a = [1, 2];
echo add(...$a); // 3

Можно задать несколько аргументов в привычном виде, а затем добавить .... В этом случае ... поместит в массив только те аргументы, которые не нашли соответствия указанным в объявлении функции.

Также можно добавить подсказку типа перед .... В этом случае PHP будет следить, чтобы все аргументы обработанные многоточием (...) были того же типа, что указан в подсказке.

function total_intervals($unit, DateInterval ...$intervals) {
    $time = 0;
	
    foreach ($intervals as $interval) {
        $time += $interval->$unit;
    }
	
    return $time;
}

$a = new DateInterval('P1D');
$b = new DateInterval('P2D');

echo total_intervals('d', $a, $b).' days';
// 3 days

echo total_intervals('d', null);
// Fatal error: Uncaught TypeError: Argument 2 passed to total_intervals() must be an instance of DateInterval, null given

Возврат значений

Значения возвращаются при помощи необязательного оператора возврата. Возвращаемые значения могут быть любого типа, в том числе это могут быть массивы и объекты. Возврат приводит к завершению выполнения функции и передаче управления обратно к той строке кода, в которой данная функция была вызвана.

Если конструкция return не указана, то функция вернет значение NULL.

function square($num) {
    return $num * $num;
}

echo square(4); // 16

Функция не может возвращать несколько значений, но аналогичного результата можно добиться, возвращая массив.

function small_numbers() {
    return array (0, 1, 2);
}

list ($zero, $one, $two) = small_numbers();

echo $zero; // 0

Объявление типов возвращаемых значений

В PHP 7 добавлена возможность объявлять тип возвращаемого значения. Аналогично объявлению типов аргументов можно задать тип значения, которое будет возвращаться функцией. Типы, которые можно объявить для возвращаемых значений те же, что и для аргументов функций.

Режим строгой типизации также работает при объявлении типа возвращаемого значения. В обычном режиме слабой типизации возвращаемое из функции значение приводится к корректному типу. При строгой типизации возвращаемое значение должно быть заданного типа, иначе будет выброшено исключение TypeError.

Начиная с PHP 7.1.0 возвращаемые значения могут быть помечены как обнуляемые, путём добавления префикса в виде вопросительного знака (?) к названию типа. Это означает, что функция возвращает либо значение указанного типа, либо значение NULL.

Если переопределяется родительский метод, возвращаемое значение дочернего метода должно быть того же типа, что и родительского. Если в родительском методе не задан тип возвращаемого значения, то в дочернем методе можно указать любой.

function sum($a, $b): float {
    return $a + $b;
}

var_dump(sum(1, 2));
// float(3)
declare(strict_types=1);

function sum($a, $b): int {
    return $a + $b;
}

var_dump(sum(1, 2));
// int(3)

var_dump(sum(1, 2.5));
// Fatal error: Uncaught TypeError: Return value of sum() must be of the type int, float returned
class C {}

function getC(): C {
    return new C;
}

var_dump(getC());
// object(C)#1 (0) { }
function get_item(): ? string {
    if (isset($_GET['item'])) {
        return $_GET['item'];
    } else {
        return null;
    }
}

Обращение к функциям через переменные

PHP поддерживает концепцию переменных функций. Это означает, что если к имени переменной присоединены круглые скобки, PHP ищет функцию с тем же именем, что и результат вычисления переменной, и пытается ее выполнить. Эту возможность можно использовать для реализации обратных вызовов, таблиц функций и множества других вещей.

Переменные функции не будут работать с такими языковыми конструкциями как echo, print, unset(), isset(), empty(), include, require и т.п. Вам необходимо реализовать свою функцию-обертку для того, чтобы приведенные выше конструкции могли работать с переменными функциями.

function foo() {
    echo "foo()";
}

function bar($arg = '') {
    echo "bar(); аргумент был '$arg'";
}

// Функция-обертка для echo
function echoit($string) {
    echo $string;
}

$func = 'foo';
$func();
// Вызывает функцию foo()
// Результат - foo()

$func = 'bar';
$func('test');
// Вызывает функцию bar()
// Результат - bar(); аргумент был 'test'

$func = 'echoit';
$func('test');
// Вызывает функцию echoit()
// Результат - test

Вы также можете вызвать методы объекта используя возможности PHP для работы с переменными функциями:

class Foo {
    function Variable() {
        $name = 'Bar';
        $this->$name();
		// Вызываем метод Bar()
    }
    
    function Bar() {
        echo "Это Bar";
    }
}

$foo = new Foo();
$funcname = "Variable";
$foo->$funcname();
// Обращаемся к $foo->Variable()
// Результат - Это Bar

При вызове статических методов вызов функции "сильнее", чем оператор доступа к статическому свойству:

class Foo {
    static $variable = 'статическое свойство';
    
	static function Variable() {
        echo 'Вызов метода Variable';
    }
}

echo Foo::$variable;
// Это выведет 'статическое свойство'. Переменная $variable будет разрешена в нужной области видимости.
// Результат - статическое свойство

$variable = "Variable";
Foo::$variable();
// Это вызовет $foo->Variable(), прочитав $variable из этой области видимости.
// Результат - Вызов метода Variable

Анонимные функции

Анонимные функции, также известные как замыкания (closures), позволяют создавать функции, не имеющие определенных имен. Они наиболее полезны в качестве значений callback-параметров, но также могут иметь и множество других применений.

Анонимные функции реализуются с использованием класса Closure.

echo preg_replace_callback('~-([a-z])~', function ($match) {
    return strtoupper($match[1]);
}, 'hello-world');

// выведет helloWorld

Замыкания также могут быть использованы в качестве значений переменных; PHP автоматически преобразует такие выражения в экземпляры внутреннего класса Closure. Присвоение замыкания переменной использует тот же синтаксис, что и для любого другого присвоения, включая завершающую точку с запятой:

$greet = function($name) {
    printf("Привет, %s", $name);
};

$greet('Мир');
$greet('PHP');

// Привет, Мир
// Привет, PHP

Стрелочные функции

Стрелочные функции появились в PHP 7.4, как более лаконичный синтаксис для анонимных функций.

И анонимные, и стрелочные функции реализованы с использованием класса Closure.

Основной вид записи стрелочных функций: fn (argument_list) => expr.

Стрелочные функции поддерживают те же возможности, что и анонимные функции, за исключением того, что использование переменных из родительской области всегда выполняется автоматически.

Когда переменная, используемая в выражении, определена в родительской области, она будет неявно захвачена по значению. В следующем примере функции $fn1 и $fn2 ведут себя одинаково:

$y = 1;

$fn1 = fn($x) => $x + $y;

// эквивалентно использованию $y по значению:
$fn2 = function ($x) use ($y) {
    return $x + $y;
};

var_export($fn1(3));
// 4

Вложенные стрелочные функции:

$fn = fn($x) => fn($y) => $x * $y;

echo $fn(5)(10); // 50