Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

web - tec / PHP 5 для начинающи

.pdf
Скачиваний:
78
Добавлен:
12.06.2015
Размер:
26.79 Mб
Скачать

492 Глава 12

Ключевое слово public используется для того, чтобы впоследствии можно было вне класса получить доступ к данной переменной. Некоторые переменные класса су+ ществуют только для использования самим классом и не должны быть доступны внешнему коду. В данном примере можно устанавливать и считывать значение свой+ ства name. Необходимо отметить, что работа метода sayHello изменилась. Теперь он получает значение из свойства, а не в виде своего параметра.

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

апоскольку имя объектной переменной заранее при создании класса неизвестно, то переменная $this позволяет обращаться к текущему экземпляру класса.

Вданном примере первый вызов метода sayHello распечатывает строку Steve,

авторой распечатывает Ed, потому что переменная $this позволяет каждому объек+ ту обращаться к самому себе, не зная имени запрашиваемой в текущий момент объ+ ектной переменной. Необходимо помнить о том, что некоторые свойства влияют на работу определенных методов, например, метод accelerate класса Car должен определить количество оставшегося топлива. Код внутри метода accelerate в та+ ком случае должен использовать конструкцию $this->amountOfFuel, чтобы по+ лучить доступ к указанному свойству.

Для получения доступа к свойствам необходим только один знак $. Синтаксис дол+ жен быть таким: $obj->property, а не $obj->$property. Часто это сбивает с толку новичков в PHP. Переменная свойства объявляется как public $property, а доступ к ней осуществляется как $obj->property.

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

Ограничение доступа к переменным экземпляра

Существует три уровня видимости методов или переменных экземпляра: откры+ тый (public), частный (private) и защищенный (protected). Открытые члены (т.е. пе+ ременные) доступны для любого кода. Частные члены доступны только для самого класса. Обычно такие члены используются для внутренних целей, например, для хра+ нения дескриптора соединения с базой данных. Защищенные члены доступны самому классу и всем его наследникам. (Определение и подробное рассмотрение наследова+ ния представлено в настоящей главе далее).

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

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

Введение в объектно*ориентированное программирование 493

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

Чтобы обойти эту проблему, рекомендуется реализовывать свойства в форме функций get[имя_Свойства] и set[имя_Свойства], как показано ниже в разделе ‘‘Практика’’.

Практика Доступ к свойствам посредством методов get и set

Внесите в файл class.Demo.php выделенные изменения:

<?php

class Demo {

private $_name;

public function sayHello() {

print "Hello {$this->getName()}!";

}

public function getName() { return $this->_name;

}

public function setName($name) {

if(!is_string($name) || strlen($name) == 0) { throw new Exception("Недопустимое имя");

}

$this->_name = $name;

}

}

?>

Отредактируйте файл testdemo.php как показано ниже:

<?php require_once('class.Demo.php'); $objDemo = new Demo();

$objDemo->setName('Steve'); $objDemo->sayHello();

$objDemo->setName(37); //генерирует ошибку

?>

Как это работает

Уровень доступа к свойству name изменился с public на private и в начале име+ ни переменной появился символ подчеркивания. Рекомендуется использовать символ подчеркивания для обозначения частных переменных и функций экземпляра. Вместе с тем это лишь соглашение и PHP этого не требует. Ключевое слово private не по+

494 Глава 12

зволяет коду, находящемуся за пределами объекта, модифицировать данное значение. Частные внутренние переменные экземпляра недоступны снаружи класса. Так как по+ лучить непосредственный доступ к этим переменным невозможно, для работы с дан+ ной информацией приходится использовать методы getName() и setName(), кото+ рые гарантируют, что класс сможет проверить значение до того, как оно будет присвоено свойству. В данном примере генерируется исключительная ситуация, если для свойства name вводится недопустимое значение. Кроме того, в код добавлен спе+ цификатор доступа public для функций.

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

Использование функций __get и __set

Настоятельно рекомендуется придерживаться совета об использовании методов get и set для всех свойств. Вместе с тем для крупных объектов с десятками свойств это может привести к необходимости создавать большое количество кода. Это особенно нежелательно, если для большинства свойств нет непосредственных требований биз+ нес+логики. В таких ситуациях оказываются полезными методы __get и __set.

Если сценарий пытается получить доступ к свойству объекта, но открытой перемен+ ной с таким именем нет, то PHP проверяет, определена ли для данного объекта функция __get. Если такая функция определена, PHP автоматически вызывает ее с целью опре+ делить значение данного свойства. Аналогично, когда значение присваивается несуще+ ствующему свойству, PHP вызывает функцию __set, если она была определена.

Практика Использование методов __get и __set

Данный пример показывает, как использовать функции __get и __set для облег+ чения работы со свойствами. Создайте файл с именем class.PropertyObject.php и введите в него следующий код:

<?php

class PropertyObject {

private $_properties = array( 'name' => null, 'dateofbirth' => null

);

function __get($propertyName) {

if(!array_key_exists($propertyName, $this->_properties)) throw new Exception('Недопустимое значение свойства!');

if(method_exists($this, 'get' . $propertyName)) {

return call_user_func(array($this, 'get' . $propertyName));

}else {

return $this->_properties[$propertyName];

}

}

function __set($propertyName, $value) {

if(!array_key_exists($propertyName, $this->_properties)) throw new Exception('Недопустимое значение свойства!');

Введение в объектно*ориентированное программирование 495

if(method_exists($this, 'set' . $propertyName)) {

return call_user_func(

array($this, 'set' . $propertyName), $value

);

} else {

$this->_properties[$propertyName] = $value;

}

}

function setDateOfBirth($dob) {

if(strtotime($dob) == -1) {

throw new Exception("Недопустимое значение даты рождения!");

}

$this->_properties['dateofbirth'] = $dob;

}

function sayHello() {

//$this->_properties['name'] и $this->_properties['dateofbirth'] //доступны посредством метода __get

print "Привет! Меня зовут $this->name. Я родился $this->dateofbirth";

}

}

?>

Чтобы протестировать этот новый класс, создайте файл testpropertyobject.php со следующим кодом:

<?php require_once('class.PropertyObject.php');

$obj = new PropertyObject();

$obj->name = 'Борис'; //значение "Борис" присваивается переменной

//$_properties['name'] с помощью метода __set $obj->dateofbirth = '5 марта, 1977'; //Метод setDateOfBirth вызывается

функцией __set

$obj->sayHello();

$obj->dateofbirth = 'blue'; //Генерирует исключение

?>

Откройте страницу testpropertyobject.php в Web+браузере. На странице должно появиться сообщение: ‘‘Привет! Меня зовут Борис. Я родился 5 марта, 1977’’ и сообщение об ошибке, которое дает понять, что значение 'blue' не подходит для свойства dateofbirth.

Как это работает

Класс позволяет определить все допустимые свойства в массиве, элементы которо+ го инициализируются с некоторым приемлемым значением по умолчанию (в данном случае Null). PHP автоматически направляет функции __get любые попытки обра+ титься к свойствам, для которых не существует открытых переменных.

Вданном примере функция __get сначала проверяет, определено ли это свойство

вчастном массиве $_properties, и если нет, то генерируется исключительная ситуация. Если свойство определено, то проверяется наличие функции get[имя_Свойства]. Ес+

ли такая функция существует, то она вызывается посредством метода call_user_func. Он вызывает процедурную функцию, если первый из переданных ему параметров

496 Глава 12

является строкой (и эта строка содержит имя вызываемой функции). Если первым па+ раметром является массив в форме array($объект, $имяМетода), то функция __get вызывает метод $имяМетода объекта $объект. В таком случае вызывается функция get[имяСвойства] для объекта $this. Если функции get[имяСвойства] нет, то просто возвращается значение элемента массива $_properties, индекс которого со+ ответствует имени свойства.

То же самое происходит и с методом __set. Сначала проверяется определение свойства, затем вызывается функция с именем set[имяСвойства] (если она сущест+ вует), которой передается значение, присваиваемое свойству. Если такой функции нет, то значение присваивается элементу массива $_properties, ключ которого ра+ вен имени данного свойства.

В представленном примере метод setDateOfBirth проверяет дату, которая при+ сваивается этому свойству. Попытка присвоить строку 'blue' свойству, в котором должна храниться дата рождения, приводит к генерированию исключительной ситуации.

Эффективность подобной методики заключается в том, что она позволяет исполь+ зовать простые переменные для хранения значений свойств, а кроме того, создавая новую функцию, можно легко добавить бизнес+логику и проверку данных без необхо+ димости переписывать код, использующий данный класс. Стоит также упомянуть, что синтаксис $obj->myProperty='foo' для присвоения значений свойств более поня+ тен, чем вызов функции для присвоения простого свойства. Класс PropertyObject в этой и в следующей главе рассматривается еще несколько раз и постепенно улучша+ ется. К концу следующей главы будет создан мощный вспомогательный класс, кото+ рый можно будет использовать во многих проектах.

Инициализация объектов

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

Например, класс PropertyObject можно переписать следующим образом:

<?php

class PropertyObject {

private $_properties;

public function __construct() { $this->_properties = array(); $this->_properties['name'] = null; $this->_properties['dateofbirth'] = null;

}

. . . //остальной код, опущенный для краткости

Функция __construct вызывается автоматически, когда создается новый объект класса PropertyObject.

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

Введение в объектно*ориентированное программирование 497

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

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

Практика Определение свойств объекта в конструкторе

Сохраните следующий код в файле class.User.php. Невыделенный код можно скопировать из файла class.PropertyObject.php. Для использования данного примера понадобится работающая MySQL+база данных. Чтобы использовать другую СУБД, можно просто заменить mysql_+функции функциями, соответствующими ис+ пользуемой платформе. Кроме того, следует удостовериться, что строка подключения соответствует требованиям используемой среды.

<?php

class User {

private $_properties; private $_hDB;

public function __construct($userID) { $this->_properties = array(); $this->_changedProperties = array(); $this->_properties['id'] = null; $this->_properties['username'] = null; $this->_properties['realname'] = null;

$this ->_hDB = mysql_connect('localhost', 'dbuser', 'mypassword');

if(! is_resource($this->_hDB)) {

throw new Exception("Невозможно подключиться к базе данных!");

}

$connected = mysql_select_db('mydatabase', $this ->_hDB);

if(! $connected) {

throw new Exception("Невозможно использовать базу данных 'mydatabase'!");

}

$sql = "select * from users where id = $userID"; $rs = mysql_query($sql, $this->_hDB);

if(! mysql_num_rows($rs)) {

throw new Exception("Пользователя с идентификатором $userID не существует!");

}

$row = mysql_fetch_assoc($rs);

$this->_properties['id'] = $row['id']; $this->_properties['username'] = $row['username']; $this->_properties['realname'] = $row['realname'];

}

498Глава 12

//Методы __get и __set для краткости опущены

//Запрещаем изменение идентификатора пользователя function setID($value) {

throw new Exception('Изменение идентификатора пользователя не допускается!');

}

function sayHello() {

print "Привет! Меня зовут {$this->realname}. Мой идентификатор: {$this->id}";

}

}

?>

Создайте файл testuser.php и введите в него следующий код:

<?php require_once('class.User.php');

$obj = new User(27); //Пользователь Боб Смит

$obj->sayHello();

//Выводит на экран строку "Привет! Меня зовут Боб Смит. Мой идентификатор: 27"

?>

Чтобы создать в MySQL+базе данных таблицу пользователей, можно ввести сле+ дующий SQL+оператор:

CREATE TABLE users (

id int NOT NULL AUTO_INCREMENT PRIMARY KEY, username varchar(50),

realname varchar(255) );

Используя приведенный ниже оператор INSERT, можно заполнить только что соз+ данную таблицу данными для тестирования класса:

mysql> INSERT INTO users (id, username, realname) VALUES( -> 27,

-> 'bsmith', -> 'Боб Смит');

Если передаваемый конструктору идентификатор пользователя отсутствует в базе данных и код создания объекта не был заключен в блок try... catch..., то на эк+ ране появится примерно следующее сообщение:

Fatal error: Uncaught exception 'exception' with message 'No user exists with id 12345!' in /path/to/class.User.php:28 Stack trace: # 0 /path/to/testuser.php(4): User->_ construct() # 1 {main} thrown in

/path/to/class.User.php on line 28

(Неисправимая ошибка: не перехваченное исключение 'exception'

с сообщением 'Пользователя с идентификатором 12345 не существует!' файл

/path/to/class.User.php:28 Трассировка стека: # 0

/path/to/testuser.php(4): User->_ construct() # 1 {main} генерируется в файле /path/to/class.User.php строка 28)

Введение в объектно*ориентированное программирование 499

Как это работает

Для хранения дескриптора ресурса подключения к базе данных создается частная переменная экземпляра $_hDB. Затем добавляется метод+конструктор __construct, которому передается параметр $userID. При создании новых объектов класса User конструктору передается идентификатор пользователя, данные которого необходимо выбрать из базы. Это значение представляет первичный ключ таблицы, в которой хранится информация о данном пользователе.

Конструктор открывает соединение с базой данных и сохраняет дескриптор ре+ сурса в переменной $this->_hDB. Затем необходимо вызвать функцию mysql_select_db для указания имени базы данных, с которой требуется установить связь. После этого вводится оператор SELECT для получения из базы данных всей информации о пользователе. Полученная информация записывается в массив $_properties, где она будет использоваться функциями __get, __set либо функ+ циями get[имяСвойства] или set[имяСвойства].

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

Использование конструктора с параметром позволяет инициализировать все зна+ чения свойств, извлекая их из базы данных. В данном случае извлекается информация о пользователе. Конечно, в реальных приложениях необходимо убедиться, что строка 4 файла testuser.php заключена в блок try... catch..., и перехватываются все ошибки, которые могут возникать при подключении к базе данных или выборке ин+ формации о пользователе.

Кроме того, следует отметить, что функция setID генерирует исключение при попытке изменить свойство, в котором хранится идентификатор пользователя. Так как данное свойство является первичным идентификатором пользователя, не реко+ мендуется разрешать его изменение со стороны пользователей класса.

В случае создания класса, который не требует какого+либо особого кода инициали+ зации, создавать конструктор нет необходимости. Как показано в предыдущем при+ мере очень простого класса Demo, PHP автоматически выполняет все нужные для соз+ дания объекта операции. Функцию конструктора рекомендуется создавать, только когда это действительно необходимо.

Уничтожение объекта

Создаваемая в сценарии объектная переменная удаляется из системной памяти, когда запрашиваемая страница полностью завершила свою работу, когда переменная выходит за пределы области видимости или когда она явно устанавливается в Null. В PHP5 можно перехватывать уничтожение объекта и предпринимать в этот момент какие+либо действия. Чтобы это сделать, необходимо создать функцию с именем __destruct, не имеющую параметров. Функция+деструктор, если она существует, вы+ зывается автоматически перед удалением объекта.

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

500 Глава 12

Практика Создание деструктора

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

Внесите в файл class.User.php следующие изменения:

<?php

class User {

private $_properties;

private $_changedProperties; //содержит список

//измененных свойств

private $_hDB;

// методы __construct и __get для краткости опущены

function __set($propertyName, $value) {

if(!array_key_exists($propertyName, $this->_properties)) throw new Exception('Недопустимое значение свойства!');

if(method_exists($this, 'set' . $propertyName)) { return call_user_func(

array($this, 'set' . $propertyName), $value

);

} else {

//Если значение свойства действительно было изменено, //но еще не попало в массив changedProperties,

//то добавить его. if($this->_properties[$propertyName] != $value &&

!in_array($propertyName, $this->_changedProperties)) { $this->_changedProperties[] = $propertyName;

}

//Устанавливаем значение свойства $this->_properties[$propertyName] = $value;

}

}

//запрещаем изменение значения пользовательского идентификатора

function setID($value) {

throw new Exception('Изменение пользовательского идентификатора не допускается!');

}

function sayHello() {

print "Привет! Меня зовут {$this->realname}. Мой идентификатор: {$this->id}";

}

Введение в объектно*ориентированное программирование 501

function __destruct() {

//Проверяем наличие изменений. Если они есть, //то сохраняем из в базе данных.

if(sizeof($this->_changedProperties)) {

$sql = "UPDATE users SET ";

//Формируем SQL-оператор путем создания

//массива операторов и их последующего объединения. //В конце каждого оператора добавляется запятая.

$setStatements = array(); foreach($this->_changedProperties as $prop) {

$setStatements[] = "$prop = '{$this->_properties[$prop]}'";

}

//создаем строку

$sql .= join(', ', $setStatements);

//присоединяем предложение WHERE

$sql .= " WHERE userid = $this->id"; $hRes = mysql_query($sql);

}

//Закрываем подключение к базе данных – все закончено. mysql_close($this->_hDB);

}

}

Измените файл testuser.php, как показано ниже:

<?php require_once('class.User.php');

$obj = new User(27); //Пользователь Роберт Смит

$obj->realname = 'Боб Смит';

$obj->sayHello();

$obj2 = new User(34); //Джейн Доу $obj2->sayHello();

?>

Как это работает

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

Соседние файлы в папке web - tec