Главное меню

Создание функций

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

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

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

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

Базовая структура функции

Объявление функции создает, или объявляет (declare), новую функцию. Объявление начинается с ключевого слова function, оно присваивает функции имя, задает необходимые параметры и содержит код, который выполняется при каждом вызове функции.

Объявление простейшей функции имеет примерно такой вид:

function my_function() {

echo 'Вызвана моя функция';

}

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

my_function();

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

Встроенные функции доступны для всех РНР-сценариев, но если вы объявляете свои собственные функции, то они доступны только для того сценария (сценариев), где они были объявлены. Имеет смысл завести отдельный файл и поместить в него все обычно используемые функции. Тогда в каждом из своих сценариев вы можете с помощью оператора require() получить доступ к этим функциям.

Внутри функции код, который решает требуемую задачу, заключается в фигурные скобки. Между этими скобками можно поместить любые конструкции, допустимые в любом месте РНР-сценария, в том числе вызовы функций, объявления новых переменных или функций, операторы require() или include() и обычный HTML-текст. Если внутри функции нужно выйти из среды РНР и ввести обычный HTML-текст, это делается так же, как в любом другом месте сценария, т.е. устанавливается закрывающий РНР-дескриптор, за которым помещается HTML-текст. Ниже показан допустимый вариант ранее приведенного примера, который генерирует такой же вывод:

<?php

function my_function () {

?>

Вызвана моя функция

<?php

}

?>

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

Именование функций

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

На имена функций накладываются следующие ограничения.

• Функция не может иметь то же имя, что у существующей функции.

• Имя функции может содержать только буквы, цифры и символы подчеркивания.

• Имя функции не может начинаться с цифры.

Многие языки программирования допускают многократное использование имен функций. Это свойство называется перегрузкой функций (function overloading). Однако РНР не поддерживает перегрузку функций, поэтому функция не может иметь имя, совпадающее с именем любой встроенной или существующей пользовательской функции. Имейте в виду, что хотя любой РНР-сценарий распознает все встроенные функции, тем не менее, определяемые пользователем функции существуют только в тех сценариях, в которых они объявлены. Это означает, что имя функции можно повторно использовать в другом файле, но это может привести к путанице, поэтому подобных ситуаций следует избегать.

Следующие имена функций являются допустимыми:

name()

name2()

name_three()()

_namefour()

А эти имена недопустимы:

5name()

name-six()

fopen()

(Последнее имя могло бы быть допустимым, если бы оно не было именем уже существующей функции.)

Обратите внимание, что хотя $name не является допустимым именем функции, следующий вызов функции:

$name();

может нормально работать, в зависимости от значения, хранящегося в $name. Причина состоит в том, что РНР берет значение переменной $name, ищет функцию с именем, совпадающим с этим значением, и пытается к ней обратиться. Данный тип функции носит название функции-переменной и часто оказывается весьма полезным.

Параметры

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

function create_table($data) {

echo "<table border = 1>";

reset($data); // Вспомните, что это делается для указания на начало

$value = current($data);

while ($value) {

echo "<tr><td>".$value."</td></tr>\n";

$value = next($data);

}

echo "</table>";

}

Если вызвать функцию create_table() следующим образом:

$my_array = array('Строка один.', 'Строка два.', 'Строка три.');

create_table ($my_array);

то получим вывод, показанный на рис. 5.4.

Создание функций

Параметр дает возможность передать в функцию данные, которые были созданы за ее пределами, – в данном случае, массив $data.

Как и в случае встроенных функций, пользовательские функции могут принимать несколько параметров и иметь необязательные параметры. Мы можем усовершенствовать функцию create_table() различными способами, одним из них могло бы быть предоставление пользователю возможности задавать параметры рамки или другие атрибуты таблицы. Ниже приведена улучшенная версия этой функции. Она похожа на предыдущую версию, но позволяет при желании определить ширину рамки, расстояние между ячейками и способ заполнения ячеек.

function create_table2($data, $border=1, $cellpadding=4, $cellspacing=4) {

echo "<table border=\"".$border."\"cellpadding=\"".$cellpadding"."\"cellspacing=\"".$cellspacing."\">";

reset($data);

$value = current($data);

while ($value) {

echo "<tr><td>".$value."</td></tr>\n";

$value = next($data);

}

echo "</table>";

}

$my_array = array('Строка один.', 'Строка два.', 'Строка три.');

create_table($my_array);

Первый параметр функции create_table2() по-прежнему обязателен. Следующие три являются необязательными, поскольку для них определены значения по умолчанию. Вывод, похожий на показанный на рис. 5.4, получается в результате следующего вызова функции create_table2() :

create_table2($my_array);

Если мы хотим, чтобы эти же данные отображались в более просторной форме, данную функцию можно вызвать так:

create_table2($my_array, 3, 8, 8) ;

Необязательные параметры можно передавать не все, а лишь некоторые из них. Параметры присваиваются слева направо.

Не забывайте, что нельзя пропустить какой-либо необязательный параметр и после него задать следующий параметр. В данном примере, если мы хотим задать значение параметра cellspacing, нам придется передать также и значение параметра cellpadding. Это ограничение часто становится причиной ошибок программирования. Именно поэтому необязательные параметры помещаются в конце любого списка параметров.

Следующий вызов функции:

create_table2($my_array, 3) ;

вполне допустим; он приводит к тому, что значение переменной $border устанавливается равным 3, а значениям переменных $cellpadding и $cellspacing получают соответствующие значения по умолчанию.

Существует возможность объявлять функции, которые принимают переменное количество параметров. Определить, сколько параметров передано, а также их значения, можно посредством вспомогательных функций func_num_args(), func_get_arg() и func_get_args().

Для примера рассмотрим следующую функцию:

function var_args() {

echo "Количество параметров: ";

echo func_num_args();

echo "<br />";

$args = func_get_args () ;

foreach ($args as $arg) {

echo $arg."<br />";

}

}

Эта функция выводит количество переданных ей параметров вместе с их значениями. Функция func_num_args() возвращает количество переданных аргументов, а функция func_get_args() – массив, содержащий эти аргументы. В качестве альтернативы можно получать доступ к каждому аргументу с помощью функции func_get_arg(), передавая ей номер требуемого аргумента. (Нумерация аргументов начинается с нуля.)

Область действия

Вы, должно быть, уже обратили внимание на то, что при необходимости использования переменных внутри затребованного или включаемого файла мы просто объявляем их в сценарии перед оператором require() или include(). Однако при использовании функции эти переменные передаются в нее явно. Частично это связано с тем, что не существует никакого механизма для явной передачи переменных в затребованный или включаемый файл, а частично с тем, что область действия переменных в функциях определяется иначе.

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

• Переменные, которые объявлены внутри функции, действуют в области от оператора, в котором они объявлены, до закрывающей скобки в конце функции. Эта область называется областью действия функции, а сами переменные – локальными (local) переменными.

• Переменные, которые объявлены за пределами функции, действуют в области от оператора, в котором они объявлены, до конца файла, но не внутри функций. Эта область называется глобальной областью действия, а такие переменные – глобальными (global) переменными.

• Специальные суперглобальные переменные видны как внутри функции, так и за ее пределами.

• Использование операторов require() и include() не влияет на область действия переменных. Если оператор используется внутри функции, его областью действия является область действия функции. Если он используется за пределами функции, его областью действия является глобальная область.

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

• Переменные могут быть вручную удалены посредством вызова функции unset($имя_переменной). Если переменная удалена, она пропадает из своей области действия.

Приведенные далее примеры помогут внести ясность в данные определения.

Следующий код не генерирует никакого вывода. В нем переменная $var объявляется внутри функции fn(). Поскольку эта переменная объявляется внутри функции, она имеет область действия в рамках функции и существует в области от места ее объявления до конца функции. При обращении к переменной $var за пределами функции, создается новая переменная с именем $var. Эта новая переменная имеет глобальную область действия и будет видимой до конца файла. К сожалению, если единственным оператором, применяемым в отношении этой новой переменной $var, будет echo, она никогда не будет иметь значения.

function fn() {

$var = "значение";

}

fn();

echo $var;

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

function fn() {

echo "внутри функции \$var = ".$var."<br />";

$var = "значение 2";

echo "внутри функции, \$var = ".$var."<br />";

}

$var = "значение 1";

fn(); .

echo "вне функции \$var. = ".$var."<br />";

Этот код сгенерирует следующий вывод:

внутри функции $var =

внутри функции $var = значение 2

вне функции $var = значение 1

Функции не выполняются до тех пор, пока они не будут вызваны, поэтому первым выполняемым оператором является $var = "значение 1";. Он создает переменную $var, имеющую глобальную область действия и содержимое "значение 1". Следующий выполняемый оператор – вызов функции fn(). Строки внутри оператора выполняются по очереди. Первая строка в функции обращается к переменной $var. Когда эта строка выполняется, она не может видеть созданную до этого переменную $var, поэтому она создает новую переменную, имеющую область действия в рамках функции, и выводит ее. В результате создается первая строка вывода.

Следующая строка внутри функции устанавливает содержимое переменной $var равным "значение 2". Поскольку действия выполняются внутри функции, эта строка изменяет значение локальной переменной $var, а не глобальной. Вторая строка вывода подтверждает выполнение этого изменения.

На этом выполнение функции завершается, поэтому выполняется заключительная строка сценария. Этот оператор echo показывает, что значение глобальной переменной не изменилось.

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

function fn() {

global $var;

$var = "значение";

echo "внутри функции \$var = ".$var."<br />";

}

fn();

echo "вне функции \$var = ".$var."<br />";

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

внутри функции $var = значение

вне функции $var = значение

Обратите внимание, что переменная определена в области действия, начинающейся с того места, в котором выполняется строка global $var;. Функцию можно было бы объявить выше или ниже того места, в котором она вызывается. (Также обратите внимание, что область действия функции существенно отличается от области действия переменной!) Место объявления функции фактически не играет роли – важно лишь то, где функция вызывается и, следовательно, где выполняется содержащийся внутри нее код.

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

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

Передача по ссылке и передача по значению

Если требуется создать функцию по имени increment(), позволяющую увеличивать целочисленное значение на единицу, можно попытаться решить эту задачу следующим образом:

function increment($value, $amount = 1) {

$value = $value + $amount;

}

Однако этот код не работает. В результате выполнения показанного ниже теста выводится значение 10.

$value =10;

increment($value);

echo $value;

Как видим, содержимое переменной $value не изменилось.

Такой результат является следствием правил, регламентирующих область действия. Представленный выше код создает переменную $value, которая содержит значение 10. Затем вызывается функция increment(). Переменная $value создается в функции во время вызова функции. К значению этой переменной добавляется 1, поэтому внутри функции значение $value равно 11 до тех пор, пока выполнение функции не завершается и не осуществляется возврат в вызвавший ее код. В вызвавшем коде в качестве переменной $value выступает другая переменная, определенная в глобальной области, а она остается неизменной.

Один из способов решения этой проблемы предполагает объявление переменной $value в функции в качестве глобальной, но это означает, что для использования этой функции переменную, значение которой требуется увеличить, нужно назвать $value. Более рациональным подходом было бы использование передачи по ссылке.

Обычный способ вызова параметров функции называется передачей по значению. Когда вы передаете параметр, создается новая переменная, которая содержит передаваемое значение. Она представляет собой копию исходной переменной. Данное значение можно изменять как угодно, но при этом значение исходной переменной вне функции остается неизменным. (Вообще-то это несколько упрощенное описание происходящего внутри РНР.)

Более рациональный подход предусматривает использование передачи по ссылке. В этом случае при передаче параметра, вместо того чтобы создавать новое значение, функция принимает ссылку на исходную переменную. Эта ссылка имеет имя переменной, начинающееся со знака доллара ($), и может использоваться совершенно так же, как и любая другая переменная. Различие состоит в том, что вместо того, чтобы иметь собственное значение, она просто ссылается на исходную переменную. Любые изменения, применяемые к ссылке, влияют также и на оригинал.

Передача используемого параметра по ссылке указывается путем помещения символа амперсанда (&) перед его именем в определении функции. Никакие изменения в вызове функции не требуются.

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

function increment(&$value, $amount = 1) {

$value = $value + $amount;

}

Теперь в нашем распоряжении работающая функция, и мы можем назвать переменную, значение которой хотим увеличить, как нам заблагорассудится. Как уже упоминалось, использование одного и того же имени внутри и за пределами функции может привести к путанице, поэтому переменной в основном сценарии мы присвоим новое имя. Теперь следующий тестовый код будет выводить на экран значение 10 перед обращением к функции increment() и 11 – после него:

$а = 10;

echo $а.'<br />';

increment ($а);

echo $а ' <br />' ;

Ключевое слово return

Ключевое слово return останавливает выполнение функции. Когда выполнение функции завершается либо из-за того, что все операторы выполнены, либо по причине встречи ключевого слова return, управление возвращается оператору, следующему за вызовом функции.

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

function test_return () {

echo "Этот оператор будет выполнен";

return;

echo "Этот оператор не будет выполнен";

}

Очевидно, это не самый полезный способ использования оператора return. Обычно возврат из функции требуется только в случае выполнения некоторого условия.

Условие возникновения ошибки – это наиболее распространенная причина применения оператора return с целью преждевременного прекращения выполнения функции. Если, например, вы написали функцию для определения большего из двух чисел, возможно, вы захотите выйти из нее в случае отсутствия одного из чисел.

function larger($x, $у) {

if (!isset($x) || !isset($y)) {

echo "Эта функция требует указания двух чисел";

return;

}

if ($х >= $у) {

echo $x."<br />";

} else {

echo $y."<br />";

}

}

Встроенная функция isset() сообщает, была ли создана переменная, и было ли ей присвоено значение. Данный код генерирует сообщение об ошибке и выполняет возврат, если значение какого-либо из параметров не установлено. Эта проверка выполняется с помощью выражения !isset(), означающего "НЕ isset()", и следовательно, условный оператор if можно прочесть как "если значение х не установлено или если значение у не установлено". Функция будет выполнять возврат, если любое из этих условий истинно.

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

Результат работы следующего кода:

$а = 1;

$b = 2.5;

$c = 1.9;

larger($а, $b) ;

larger($с, $а) ;

larger($d, $а) ;

будет иметь такой вид:

2.5

1.9

Эта функция требует указания двух чисел

Возврат значений из функции

Выход из функции – не единственная причина применения оператора return. Во многих функциях операторы return используются для обмена данными с вызывающим их кодом. Функция larger() была бы более полезной, если бы вместо вывода на экран результата сравнения она возвращала само число. В этом случае вызвавший функцию код мог бы принимать решение, нужно ли и когда именно нужно отображать или использовать это большее число. Эквивалентная встроенная функция max() именно так и работает.

Функцию larger() можно определить следующим образом:

function larger ($х, $у) {

if (!isset($х) | | !isset($у)) {

return false;

} else if ($x>=$y) {

return $x;

} else {

return $y;

}

}

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

Для сравнения: встроенная функция max() ничего не возвращает, если обе переменные не установлены, а если только одна переменная была установлена, функция возвращает значение именно этой переменной.

Показанный ниже код:

$а = 1; $b = 2.5; $с = 1.9;

echo larger($а, $b)."<br />";

echo larger($c, $a)."<br />";

echo larger($d, $a)."<br />";

генерирует следующий вывод, поскольку переменная $d не существует, a false на экран не выводится:

2.5

1.9

Часто функции, которые выполняют определенную задачу, и в то же время не должны возвращать конкретных значений, возвращают true или false, чтобы указать на успешное или неудачное выполнение этой задачи. Значения true и false могут быть, соответственно, представлены целыми значениями 1 или 0, хотя это значения другого типа.

Реализация рекурсии

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

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

Ради полноты изложения рассмотрим краткий пример, показанный в листинге 5.5.

Создание функций

В этом листинге представлены реализации двух функций. Обе они выводят строку в обратном порядке. Функция reverse_r() – рекурсивная, a reverse_i() – итеративная.

Функция reverse_r() принимает строку в качестве параметра. При ее вызове она будет вызывать саму себя, каждый раз передавая символы строки со второго до последнего.

Например, если вызвать функцию следующим образом:

reverse_r('победа');

она вызовет себя шесть раз со следующими параметрами:

reverse_r('обеда');

reverse_r('беда');

reverse_r('еда');

reverse_r('да');

reverse_r('а');

reverse_r('');

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

При каждом вызове проверяется длина переданной строки. По достижении конца строки условие оказывается ложным (strlen() == 0). После этого последний экземпляр функции (revetse_r (' ')) продолжит работу и выполнит следующую строку кода, т.е. выведет на экран первый символ переданной строки – в данном случае никакого вывода не будет, поскольку строка пуста.

Затем этот экземпляр функции возвращает управление экземпляру, который вызвал его, а именно, reverse_r('а'). Этот экземпляр выводит первый символ в своей строке, 'а', и возвращает управление вызвавшему его экземпляру.

Упомянутый процесс – вывод символа и возврат к экземпляру функции, расположенному над ним в порядке вызова, – продолжается до тех пор, пока управление не будет возвращено основной программе.

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

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

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

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

Пространства имен

В общем случае пространство имен (namespace) – это абстрактный контейнер для группы идентификаторов. В РНР это означает, что пространство имен может содержать определенные вами функции, константы и классы. Создание пространств имен имеет несколько организационных преимуществ для собственных функций и определений классов.

• В начало имен функций, классов и констант в пространстве имен автоматически приписывается название этого пространства имен.

• Неуточненные имена классов, функций и констант разрешаются во время выполнения, причем поиск сначала выполняется в пространстве имен, а потом в глобальном пространстве.