Если вы пользуетесь одной из предшествующих версий РНР, то должны принимать во внимание множество важных отличий.
В РНР4 объекты передавались по значению, а сейчас они передаются по ссылке. Это никоим образом не разрушает старые коды, и многие программисты зачастую пишут неэффективный код, не давая себе отчет в этом. Например, следующий код:
$c = new myClass;
создает новый экземпляр класса и затем копирует его в $c (фактически создавая две копии и тут же теряя дескриптор одной из них). Такое поведение может привести к проблемам, если вы предполагаете, что объекты передаются по ссылке, в частности, в функции.
Большинство объектно-ориентированных языков передают объекты по ссылке по умолчанию, и РНР теперь попадает в эту категорию.
Другое существенное отличие заключается в том, что ранее в РНР были сложности с разименованием объектов, возвращаемых из функций, обычно для вызова методов этих объектов. Ранее, например, вы не могли записать следующий код:
select_object() -> display();
в котором функция select_object() возвращала объект, имеющий метод с именем display(). Теперь этот оператор работает без проблем.
В РНР5 внедрена идея констант класса. Константа класса может использоваться без необходимости создания экземпляров класса, как показано в следующем примере:
<?php
class Math {
const pi = 3.14159;
}
echo 'Math::pi = '.Math::pi."\n";
?>
Доступ к константам класса осуществляется с помощью операции :: и указания имени класса, которому константа принадлежит.
В РНР5 также введено ключевое слово static. Оно применяется к методам, позволяя им быть вызванными без необходимости создания экземпляра класса. Идея статических методов подобна идее констант класса. Например, вернемся еще раз к классу Math, который упоминался в предыдущем примере. Мы можем добавить к нему статический метод squared () и вызвать его, не создавая экземпляра класса:
class Math
{
static function squared ($input)
{
return $input*$input;
}
}
echo Math::squared (8);
Обратите внимание, что внутри статического метода нельзя использовать ключевое слово this, поскольку может не существовать ни одного экземпляра для ссылки.
Также новым в РНР5 является ключевое слово instanceof и концепция указания типов (type hinting). Ключевое слово instanceof позволяет проверять тип объекта. Вы можете проверить, является ли объект экземпляром заданного класса, унаследован ли он от определенного класса либо реализует ли он некоторый интерфейс. Это ключевое слово фактически представляет собой условную операцию. Принимая во внимание предыдущие примеры, в которых класс В был реализован как субкласс класса А, рассмотрим следующие примеры использования instanceof:
Во всех примерах предполагается, что А, В и Displayable находятся в текущей области видимости; в противном случае возникает ошибка.
Также новой идеей, внедренной в Также новой идеей, внедренной в РНР5, является указание типов. Обычно при передаче параметра в РНР-функцию тип этого параметра не указывается. С помощью указания типов можно задать тип класса, который должен передаваться, и если тип фактического параметра не совпадает с ним, генерируется ошибка. Проверка типов эквивалентна instanceof. Например, предположим, что имеется следующая функция:
function check_hint (B $someclass)
{
//. . .
}
В этой функции указано, что параметр $someclass должен быть экземпляром класса В. Если передать в функцию экземпляр класса А, т.е.:
check_hint($a);
будет получена фатальная ошибка:
Catchable fatal error: Argument 1 passed to check_hint() must be an instance of B
Следует отметить, что если указать для параметра тип класса А и затем передать в функцию экземпляр класса В, ошибки не возникнет, поскольку В унаследован от А.
В РНР5 появилось новое ключевое слово clone, которое позволяет копировать существующие объекты. Например:
$c = clone $b;
создавать копию объекта $b того же самого класса с теми же самыми значениями атрибутов.
Мы можем изменить данное поведение. Если нам необходимо нестандартное поведение clone, создадим в базовом классе метод с именем __clone(). Этот метод похож на конструктор или деструктор и не вызывается напрямую. Он вызывается, когда используется ключевое слово clone, как показано выше. Внутри метода __сlone() можно определить требуемые действия по копированию.
Очень важное свойство __clone() состоит в том, что этот метод вызывается после создания точной копии снандартным способом, то есть на данном этапе можно изменять только то, что требуется.
Чаще всего в __clone() добавляется функциональность, которая проверяет корректность копирования атрибутов класса и ссылок. Если вы создаете копию класса, содержащего ссылку на какой-то объект, и требуется, чтобы этот объект также был скопирован, данные действия должны быть добавлены в __clone().
Иногда необходимовыполнить какие-то другие действия, например, обновить базу данных на основе информации, хранящейся в экземпляре класса.
Очередным нововведением в РНР5 следует считать абстрактные классы. Для таких классов никогда не создаются экземпляры.
В РНР5 также доступны абстрактные методы, которые обеспечивают только сигнатуру метода, без его реализации, как показано ниже:
abstract operationX ($param1, $param2);
Любой класс, содержащий абстрактные методы, должен быть абстрактным:
abstract class A
{
abstract function operationX ($param1, $param2);
}
Основное применение абстрактных методов и классов связано с построением сложной иерархии классов, в которой каждый субкласс должен содержать и перекрывать определенные методы; этого же можно достигнуть и посредством интерфейсов.
Ранее мы сталкивались с несколькими методами класса специального назначения, имена которых начинались с двух подчеркиваний (__), наподобии __get, __set(), __construct() и destruct (). Еще одним примером такого метода может служить __call, который используется в РНР для перегрузки методов.
Перегрузка методов характерна для многих объектно-ориентированных языков, однако она не столь полезна в РНР, так как в основном здесь применяются гибкие типы и простые в реализации необязательные функциональные параметры.
Для использования перегрузки функций потребуется реализовать метод __call(), как показано в примере ниже:
public function __call($method, $p)
{
if ($method == 'display')
if (is_object ($p[0]))
$this -> displayObject ($p[0]);
else if (is_array ($p[0]))
$this -> displayArray ($p[0]);
else
$this -> displayScalar ($p[0]);
}
Метод __call() принимает два параметра. Первый параметр содержит имя вызываемого метода, а второй — массив параметров, передаваемых этому методу. В теле __call() принимается решение о том, какой конкретно метод из числа лежащих в основе переданного должен быть вызван. В данном случае, если методу display () должен быть передан объект, вызывается лежащий в основе метод displayObject (), если должен быть передан массив — метод displayArray(), а во всех остальных случаях — метод displayScalar ().
Для вызова показанного выше кода сначала потребуется создать экземпляр класса, содержащего метод __call() (пусть он называется overload), а затем обратиться к методу display():
$ov = new overload;
$ov -> display (array(1,2,3));
$ov -> display ('cat');
Первый вызов display() приведет к выполнению displayArray(), а второй вызов — к выполнению displayScalar().
Для того чтобы этот код работал, нам не нужна ни одна лежащая в основе реализация display().
Следующей специальной функцией является __autoload(). Это не метод класса, а стандартная функция, которая должна объявляться за пределами любых объявлений классов. Если ее реализовать, она будет автоматически вызываться при попытке создания экземпляра необъявленного класса.
Основное назначение __autoload() состоит во включении всех файлов, необходимых для создания экземпляров требуемого класса. Рассмотрим пример:
function __autoload($name)
{
include_once $name. '.php';
}
Эта реализация предпринимает попытку включить файл с именем, совпадающий с именем класса.
Исключительно полезная характеристика нового объектно-ориентированного механизма связана с возможностью использования цикла foreach() для выполнения итерации по атрибутам объекта подобно тому, как это делается в отношении элементов массива. Рассмотрим пример:
class myClass
{
public $a = 5;
public $b = 7;
public $c = 9;
}
$x = new myClass;
foreach ($x as $attribute)
echo $attribute. '<br>';
В окне веб-браузера это будет выглядеть ТАК.
Если требуется получить более сложное поведение, необходимо реализовать итератор. Для этого создадим класс, для которого должна выполняться итерация через реализацию интерфейса IteratorAggregate, и напишем метод getIterator, возвращающий экземпляр класса итератора. Класс должен реализовать интерфейс Iterator, содержащий набор методов, которые должны быть реализованы.
<?php
class ObjectIterator implements Iterator {
private $obj;
private $count;
private $currentIndex;
function __construct ($obj)
{
$this -> obj =$obj;
$this -> count = count ($this -> obj -> data);
}
function rewind()
{
$this -> currentIndex = 0;
}
function valid()
{
return $this -> currentIndex < $this -> count;
}
function key()
{
return $this -> currentIndex;
}
function current ()
{
return $this -> obj -> data[$this -> currentIndex];
}
function next()
{
$this -> currentIndex++;
}
}
class Object implements IteratorAggregate
{
public $data = array();
function __construct ($in)
{
$this -> data = $in;
}
function getIterator ()
{
return new ObjectIterator ($this);
}
}
$myObject = new Object (array (2,4,6,8,10));
$myIterator = $myObject -> getIterator();
for ($myIterator -> rewind(); $myIterator -> valid(); $myIterator -> next())
{
$key = $myIterator -> key();
$value = $myIterator -> current();
echo "$key => $value <br>";
}
?>
Класс ObjectIterator имеет набор функций, как того требует интерфейс Iterator:
Причина использования класса такого итератора состоит в том, что интерфейс к данным не изменился, даже если изменяется представление данных. В рассмотренном примере класс IteratorAggregate представляет собой простой массив. даже если вы решите изменить его, скажем, на хеш-таблицу или односвязный список, вы все равно сможете пользоваться стандартным интерфейсом Iterator для его обхода, несмотря на то что код реализации интерфейса Iterator изменился.
Еще одной новой "магической" функцией является __toString(). Если реализовать в классе функцию __toString(), она будет вызываться при попытке вывода класса на печать, как показано ниже:
$p = new Printtable;
echo $p;
Все, что возвращает функция __toString(), посредством echo будет выводиться на экран. Эту функцию можно реализовать, например, следующим образом:
class Printtable
{
var $testone;
var $testtwo;
public function __toString()
{
return (var_export ($this, TRUE));
}
}
Функция var_export () выводит на печать значения всех атрибутов класса.
Очень удачной новой возможностью РНР5 следует считать API-интерфейс отражения Reflection API. Отражение — это способность запрашиваемых существующих классов и объектов сообщать информацию о своих структурах и содержимом. Данная возможность исключительно полезна при взаимодействии с неизвестными или недокументированными классами, которые представлены в закодированных РНР-сценариях.
Этот API-интерфейс чрезвычайно сложен, тем не менее, давайте рассмотрим простой пример, который возможно, позволит понять основную идею. В примере используется ранее определенный класс Page. Получить всю информацию о классе Page с помощью Reflection API можно так, как показано в листинге:
require_once ('page.inc');
$class = new ReflectionClass ('Page');
echo '<pre>';
echo $class;
echo '</pre>';
Здесь для вывода на печать информации используется метод __toString() класса Reflection. Обратите внимание на дескрипторы <pre>, которые не имеют отношения к информации, выдаваемой методом __toString().