Функции, объекты, немного синтаксиса.
 

Вступление

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

Функции в PHP

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

function mySum($a,$b) { $result = $a+$b; return($result); }; 

Использование этой функции:

$result = mySum(2,3); 

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

Любая функция в PHP состоит из 4 основных частей:

  • Имени функции. Каждая функция должна иметь свое уникальное имя, в противном случае PHP выдаст ошибку о попытке переопределения функции.
  • Списка аргуметов. Этот список может быть пустым (если функция не должна получать агрументов из внешней программы). Каждый аргумент должен иметь уникальное имя, под которым он будет "известен" внутри функции. В приведенном выше примере функция имеет два аргумента с именами $a и $b.
  • Непосредственно кода функции. Функция можеи содержать внутри себя практически любой код, допустимый в PHP за исключением определения других функций и объектов (это, кстати, отличает PHP например от JavaScript, где вложенное определение функций допустимо). В нашем случае код функции занимается вычислением значения двух переданных аргументов.
  • Возвращаемого значения. Функция не обязана возвращать значение, но если сделать это необходимо, то это делается с помощью оператора return().

Для иллюстрации дальнейшего материала предлагаю использовать следующую задачу:

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

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

  1. Возврат нескольких значений в виде массива.
// Функция достает следующее значение из глобального массива $data и возвращает его // Возвращает null, если в массиве не осталось ни одного значения. function getNext($counter) { global $data; if (isset($data[$counter])) { $result = array(); $result['data'] = $data[$counter]; $counter = $counter+1; $result['counter'] = $counter; return($result); } else return(null); }; // Использование этой функции $counter = 0; // Эта переменная будет использоваться в качестве "указателя" // на текущий элемент в массиве. while($next = getNext($counter)) // Получаем следующее значение { $value = $next['data']; // Значение из массива $counter = $next['counter']; // Новое значение указателя // Здесь мы каким-то образом используем полученные данные }; 

Кстати, не могу удержаться, чтобы не продемонстрировать, как можно записать то же самое по-другому:

function getNext($counter) { return((isset($GLOBALS['data'][$counter]))? array('data'=>$GLOBALS['data'][$counter++],'counter'=>$counter): null); }; 

Как видите - намного компактнее, хотя с первого взгляда и менее понятно.

  1. Использование глобальных переменных.

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

function getNext() { global $data, $counter; if (isset($data[$counter])) return($data[$counter++]); else return(null); };

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

  1. Использование ссылок.

В PHP4 появилась возможность работать со ссылками (references). Это очень мощный механизм, о котором мы поговорим подробнее немного позже. Если вы знакомы с C/C++, то для вас references не будут чем-то новым - это аналог указателей в C. Если же нет, то вкратце объясню, что references позволяют нескольким разным переменным ссылаться на одно и то же значение. Может быть кому-то проще будет представить это как одну переменную с несколькими именами. Признаком использования references является наличие знака & перед именем переменной. А теперь посмотрим, как решить нашу задачу с использованием references.

function getNext(&$counter) { global $data; if (isset($data[$counter])) return($data[$counter++]); else return(null); };

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

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

  1. Использование статических переменных.

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

function getNext() { global $data; static $counter=0; if (isset($data[$counter])) return($data[$counter++]); else return(null); }; // Использование этой функции while($value = getNext()) { // Здесь мы каким-то образом используем полученные данные }; 

Как видите, в нашем случае использование статической переменной здорово упростило код.

Ну и еще пара вариантов решения нашей задачи:

Вариант 1: foreach($data as $value) { // Здесь мы каким-то образом используем полученные данные }; Вариант 2: reset($data); while(list($key,$value) = each($data)) { // Здесь мы каким-то образом используем полученные данные }; 

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

Еще одним важным свойством функций в PHP является поддержка функций с переменым числом аргументов, а также задание значений по умолчанию для аргументов функций. Рассмотрим, как это выглядит на примере:

// Вывод текста с заданным цветом. Цвет по-умолчанию - красный. function printColoredString($text,$color="#FF0000") { echo "<font color='$color'>$text</font>"; }; // Использование этой функции printColoredString('Немного текста'); // Текст красного цвета printColoredString('Еще немного текста','#0000FF'); // Текст синего цвета 

Как видите - если значение цвета не задано - используется значение по умолчанию, заданное в определении функции.

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

function getAvg() { $args = func_get_args(); return(array_sum($args)/func_num_args()); };

И, наконец, необходимо рассказать еще об одной интересной возможности PHP по работе с функциями. Это т.н. анонимные функции (anonymous functions). Их отличие от обычных функций состоит в том, что они не описываются заранее, а создаются непосредственно во время выполнения PHP скрипта. Подобные функции часто используются, если необходимо использовать какую-нибудь простую функцию, например в качестве callback при сортировке массива. Лучше всего использование такой функции показать на примере. Пусть массив $users содержит информацию о пользователях сайта, и имеет следующую структуру:

array( 'firstName' => <имя пользователя>, 'lastName' => <фамилия>, 'age' => <возраст> );

Теперь предположим, что нам необходимо отсортировать этот массив пользователей по их возрасту. Разумеется здесь есть несколько вариантов решения этой задачи, но нас сейчас интересует вариант с использованием анонимной функции:

$callback = create_function('$u1,$u2','return(($u1["age"]==$u2["age"])?0:(($u1["age"]>$u2["age"])?1:-1));'); usort($users,$callback);

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

function myFunction() { echo "This is my function"; }; $funcName = 'myFunction'; $funcName(); // Вызов функции myFunction() 

И напоследок хотелось бы заметить еще одну интересную особенность PHP: хотя сам по себе язык является регистрозависимым (т.е. например переменные $var и $Var - это разные переменные), имена функций в нем не являются регистрозависимыми (т.е. функции myFunction, myfunction, MYFUNCTION и mYfUnCtIOn - это одна и та же функция :-) ). Почему это так - покрыто мраком тайны, но об этом нужно помнить, особенно когда читаешь чужой код.

Объекты в PHP

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

Объекты в PHP описываются следующим образом:

class className // Определение объекта className { var $property; // Определение переменной объекта function className() // Функция, описанная внутри объекта и имеющая то же имя, что и имя { // объекта, является конструктором этого объекта (по аналогии с C++) $this->property = 0; // Переменная $this внутри методов объекта ссылается на сам объект }; function setProperty($property) // Определение метода объекта { $this->property = $property; // Обратите внимание, property в // левой части выражения - это // переменная объекта (доступ через // $this), а в правой части - аргумент // функции с тем же именем, что // и переменная объекта. }; }; 

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

// Описание объекта Man class Man { var $firstName; var $lastName; var $age; function Man($fName,$lName,$age) { $this->firstName = $fName; $this->lastName = $lName; $this->age = $age; }; function getName() { return($this->firstName." ".$this->lastName); }; function setAge($newAge) { $this->age = $newAge; }; }; // Создание экземпляра объекта $john = new Man('John','Smith',23); // Здесь мы передаем параметры непосредственно // в конструктор объекта 

Теперь переменная $john содержит экземпляр объекта Man. К слову сказать, хотя в PHP можно задавать конструкторы объектов, поддержки деструкторов для объектов до сих пор нет. Неясно, чем вызвано подобное ограничение, но это факт.

После того, как переменной присвоен созданный экземпляр объекта - с этой переменной можно делать все , что можно делать с этим объектом:

echo "Hello, my name is ".$john->getName(); echo "I'm ".$john->age." years old"; 

Кстати, я думаю, вы уже заметили, что имена переменных, являющихся переменными объектов, записываются без символа $ перед ними? Это происходит потому, что PHP рассматривает связку "(переменная-объект)->(переменная объекта)" как одну переменную. Зато это позволит вам очень элегантно делать например вот такие вещи:

$methodName = 'getName'; echo "My name is ".$john->$methodName;

Здорово, не правда ли? :-)

Но объекты как таковые не были бы так интересны, если бы существовали сами по себе. Однако это не так и самым интересным, нужным и полезным свойством объектов является их наследование. Посмотрим, как это выглядит на примере уже описанного выше объекта Man:

// Описание объекта Worker, наследованного от объекта Man class Worker extends Man { var $profession; var $salary = 500; // Заметьте, переменные объектов могут быть // инициализированы каким-то значением // уже на этапе их описания. Однако это // значение может быть только константой // (не переменной!) function Worker($fName,$lName,$age,$profession='engineer',$salary=500) { // PHP не вызывает конструктор родительского объекта автоматически, // поэтому вам необходимо делать это самим $this->Man($fName,$lName,$age); $this->profession = $profession; $this->salary = $salary; }; }; // Создаем экземпляр объекта Worker $worker = new Worker('Jack','Robertson',29,'programmer'); // Только что созданный экземпляр объекта Worker унаследовал все переменные и методы объекта Man echo "Hello, I'm ".$worker->getName();

Переменные и методы можно не только добавлять, но и переопределять методы родительского объекта:

// Создадим новый вариант объекта Worker, переопределив метод getName() class Worker extends Man { var $profession; var $salary; function getName() { return(parent::getName().", ".$this->profession); }; }; // Создаем экземпляр объекта Worker и попытаемся получить его имя: $worker = new Worker('Jack','Robertson',29,'programmer'); echo $worker->getName();

Результатом работы предыдущего примерв будет строка: "Jack Robertson, programmer". Вы заметили использование нового оператора :: ? Этот оператор позволяет вызывать из метода объекта метод одного из родительских объектов, даже если тот уже переопределен при более "поздних" наследованиях. Использование ключевого слова parent в этом выражении позволяет вызвать метод непосредственного предка этого объекта. Однако в более сложных случаях, когда степень наследования объектов выше - можно использовать непосредственно имя объекта, метод которого необходимо вызвать. В нашем случае можно было бы написать: Man::getName().

Заключение

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

 
Автор: Александр Грималовский
 
Оригинал статьи: http://www.woweb.ru/publ/59-1-0-371