web - tec / PHP 5 для начинающи
.pdf542 Глава 13
$res = mysql_query($sql, DataManager::_getConnection());
if(! ($res && mysql_num_rows($res))) {
die("Не удалось получить данные для email-адреса $emailID");
}
return mysql_fetch_assoc($res);
}
}
?>
Класс DataManager предоставляет структуры данных, используемые для наполне+ ния массива $data в подклассах класса PropertyObject. Существуют отдельные функции, возвращающие данные каждого типа. Впоследствии в этот класс будут до+ бавлены еще несколько новых функций.
Все методы класса объявлены как статические. Статические методы требуют, что+ бы все переменные экземпляров были также статическими. Чтобы использовать ме+ тоды статического класса, не нужно создавать экземпляр этого класса. В определен+ ных ситуациях это имеет смысл. Например, класс Math предоставляет такие методы, как squareRoot(), power() и cosine(), и имеет свойства, среди которых есть ма+ тематические константы e и pi. Все экземпляры этого класса выполняют одинаковые вычисления. Квадратный корень 2 не изменяется, число 4 в кубе всегда будет равно 64, а две упомянутые константы всегда будут оставаться константами. Нет необходимости создавать отдельные экземпляры этого класса, поскольку его состояние и свойства не изменяются никогда. Класс Math, реализованный таким образом, должен давать воз+ можность вызывать все его функции статически.
Класс DataManager почти такой же. Все его функции самодостаточны, а все пере+ менные экземпляра, с которыми они взаимодействуют, являются статическими. Класс не предоставляет своим пользователям никаких свойств. Таким образом, методы класса можно вызывать с помощью оператора статических методов ::. Поскольку все созданные методы являются статическими, не нужно создавать объект с помощью оператора $obj = new DataManager(); вместо этого можно использовать синтаксис вроде DataManager::getEmail().
Следует отметить использование статической переменной функции в частном ме+ тоде _getConnection(). (Использование статических переменных функций обсуж+ далось в главе 6.)
Классы Entity, Individual и Organization
Имея все поддерживающие классы, можно перейти к ядру приложения ++++++ к классу Entity и его подклассам.
Прежде всего необходимо обновить UML+диаграмму и внести изменения в иерархию объектов. Все классы кроме DataManager, который не является производным от какого+ либо другого класса, созданы как подклассы PropertyObject. Класс PropertyObject реализует абстрактный интерфейс, который называется Validator. Новая UML+ диаграмма показана на рис. 13.12.
Работа с UML и классами 543
PropertyObject
DataManager
Validator
Address
PhoneNumber
Entity
|
|
|
|
|
|
|
|
|
|
|
|
Individual |
|
|
Organization |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 13.12.
Теперь можно начинать разрабатывать классы Entity, Individual и Organization. Следующий код представляет собой полностью реализованный класс Entity:
<?php require_once('class.PropertyObject.php'); require_once('class.PhoneNumber.php'); require_once('class.Address.php'); require_once('class.EmailAddress.php');
abstract class Entity extends PropertyObject {
private $_emails; private $_addresses; private $_phonenumbers;
public function __construct($entityID) {
$arData = DataManager::getEntityData($entityID); parent::__construct($arData);
544 Глава 13
$this->propertyTable['entityid'] = 'entityid'; $this->propertyTable['id'] = 'entityid'; $this->propertyTable['name1'] = 'sname1'; $this->propertyTable['name2'] = 'sname2'; $this->propertyTable['type'] = 'ctype';
$this->_emails = DataManager::getEmailObjectsForEntity($entityID); $this->_addresses = DataManager::getAddressObjectsForEntity($entityID); $this->_phonenumbers =
DataManager::getPhoneNumberObjectsForEntity($entityID);
}
function setID($val) {
throw new Exception('Изменение значения поля id не допускается!');
}
function setEntityID($val) { $this->setID($val);
}
function phonenumbers($index) {
if(!isset($this->_phonenumbers[$index])) {
throw new Exception('Указан некорректный номер телефона!'); } else {
return $this->_phonenumbers[$index];
}
}
function getNumberOfPhoneNumbers() { return sizeof($this->_phonenumbers);
}
function addPhoneNumber(PhoneNumber $phone) { $this->_phonenumbers[] = $phone;
}
function addresses($index) {
if(!isset($this->_addresses[$index])) {
throw new Exception('Указан неправильный адрес!'); } else {
return $this->_addresses[$index];
}
}
function getNumberOfAddresses() { return sizeof($this->_addresses);
}
function addAddress(Address $address) { $this->_addresses[] = $address;
}
function emails($index) {
if(!isset($this->_emails[$index])) {
throw new Exception('Указан неправильный email-адрес!'); } else {
return $this->_emails[$index];
}
}
function getNumberOfEmails() { return sizeof($this->_emails);
}
Работа с UML и классами 545
function addEmail(Email $email) { $this->_emails[] = $email;
}
public function validate() {
//Общие процедуры проверки корректности данных
}
}
?>
Перемещение всех функций для получения и установки свойств в родительский класс PropertyObject упрощает класс Entity и гарантирует, что в нем присутствует только код, необходимый для реализации объекта.
Класс Entity объявлен как абстрактный, потому что сам по себе он бесполезен. Все объекты являются объектами либо класса Individual, либо Organization. Соз+ давать объекты класса Entity бессмысленно. Объявление класса как абстрактного препятствует созданию объектов этого класса.
В код были добавлены обращения к нескольким новым функциям класса DataManager: getEntityData() и get[x]ObjectsForEntity. Функция getEntityData() возвра+ щает данные, необходимые для создания объекта, так же как рассмотренные выше функции для обработки типов контактной информации. Ниже приведен код новых функций в файле class.DataManager.php:
//для краткости начало файла здесь не показывается
...
die("Невозможно получить данные для телефона $phoneID");
}
return pg_fetch_assoc($res);
}
public static function getEntityData($entityID) {
$sql = "SELECT * FROM entities WHERE entityid = $entityID"; $res = mysql_query($sql, DataManager::_getConnection()); if(! ($res && mysql_num_rows($res))) {
die("Failed getting entity $entityID");
}
return mysql_fetch_assoc($res);
}
?>
Чтобы добавить функции get[x]ObjectsForEntity, необходимо поместить сле+ дующий код в конец файла class.DataManager.php сразу после объявления функ+ ции getEntityData:
public static function getAddressObjectsForEntity($entityID) {
$sql = "SELECT addressid from entityaddress WHERE entityid = $entityID"; $res = mysql_query($sql, DataManager::_getConnection());
if(!$res) {
die("Невозможно получить адресные данные для записи $entityID");
}
if(mysql_num_rows($res)) { $objs = array();
while($rec = mysql_fetch_assoc($res)) { $objs[] = new Address($rec['addressid']);
}
546 Глава 13
return $objs;
}else {
return array();
}
}
public static function getEmailObjectsForEntity($entityID) {
$sql = "SELECT emailid from entityemail WHERE entityid = $entityID"; $res = mysql_query($sql, DataManager::_getConnection());
if(!$res) {
die("Невозможно получить email-адрес для записи $entityID");
}
if(mysql_num_rows($res)) { $objs = array();
while($rec = mysql_fetch_assoc($res)) { $objs[] = new EmailAddress($rec['emailid']);
}
return $objs;
}else {
return array();
}
}
public static function getPhoneNumberObjectsForEntity($entityID) { $sql = "SELECT phoneid from entityphone WHERE entityid = $entityID"; $res = mysql_query($sql, DataManager::_getConnection());
if(!$res) {
die("Невозможно получить номер телефона для записи $entityID");
}
if(mysql_num_rows($res)) { $objs = array();
while($rec = mysql_fetch_assoc($res)) { $objs[] = new PhoneNumber($rec['phoneid']);
}
return $objs;
}else {
return array();
}
}
Все эти функции принимают в качестве параметра идентификатор записи. Они выполняют запрос к базе данных, чтобы определить, существуют ли для указанной за+ писи номера телефонов, а также e+mail+ и почтовые адреса. Если это так, функции соз+ дают массив объектов EmailAddress, Address или PhoneNumber, передавая иден+ тификатор соответствующему конструктору. Затем созданный массив возвращается объекту Entity, где он сохраняется в соответствующей частной переменной.
Класс Entity выполняет всю основную работу, поэтому остается только реализо+ вать классы Individual и Organization. Создайте файл class.Individual.php
и введите в него следующий код:
<?php require_once('class.Entity.php');
require_once('class.Organization.php');
class Individual extends Entity {
public function __construct($userID) {
Работа с UML и классами 547
parent::__construct($userID);
$this->propertyTable['firstname'] = 'name1'; $this->propertyTable['lastname'] = 'name2';
}
public function __toString() {
return $this->firstname . ' ' . $this->lastname;
}
public function getEmployer() {
return DataManager::getEmployer($this->id);
}
public function validate() { parent::validate();
//специальная проверка данных частного лица
}
}
?>
Коротко и ясно. Класс Individual устанавливает несколько новых свойств, кото+ рые упрощают доступ к имени и фамилии частного лица и позволяют не использовать довольно неудобные свойства name1 и name2, определенные в классе Entity. Кроме того, в классе также определяется новый метод, getEmployer(), которому требуется новая функция в классе DataManager. Эта функция будет рассматриваться при опи+ сании класса Organization, код которого представлен ниже. Создайте файл с име+ нем class.Organization.php и введите в него следующий код:
<?php require_once('class.Entity.php'); require_once('class.Individual.php');
class Organization extends Entity {
public function __construct($userID) { parent::__construct($userID);
$this->propertyTable['name'] = 'name1';
}
public function __toString() { return $this->name;
}
public function getEmployees() {
return DataManager::getEmployees($this->id);
}
public function validate() {
parent::validate();
//специальная проверка данных организации
}
}
?>
548 Глава 13
Этот класс благодаря эффективности наследования также довольно прост. Объяв+ ляется свойство name, которое упрощает получение названия организации (свойство sname2 для организаций не используется).
Чтобы добавить в класс DataManager функции getEmployer() и getEmployee(), необходимо добавить следующий код в конец файла class.DataManager.php:
public static function getEmployer($individualID) {
$sql = "SELECT organizationid FROM entityemployee " . "WHERE individualid = $individualID";
$res = mysql_query($sql, DataManager::_getConnection());
if(! ($res && mysql_num_rows($res))) {
die("Невозможно получить данные о работодателе для $individualID");
}
$row = mysql_fetch_assoc($res);
if($row) {
return new Organization($row['organizationid']); } else {
return null;
}
}
public static function getEmployees($orgID) {
$sql = "SELECT individualid FROM entityemployee " . "WHERE organizationid = $orgID";
$res = mysql_query($sql, DataManager::_getConnection());
if(! ($res && mysql_num_rows($res))) {
die("Невозможно получить данные о работнике для $orgID");
}
if(pg_num_rows($res)) { $objs = array();
while($row = mysql_fetch_assoc($res)) {
$objs[] = new Individual($row['individualid']);
}
return $objs; } else {
return array();
}
}
Две эти функции зависят от присутствия таблицы entityemployee, которая по+ казана ниже:
CREATE TABLE entityemployee ( individualid int NOT NULL, organizationid int NOT NULL,
CONSTRAINT fk_entityemployee_individualid
FOREIGN KEY (individualid) REFERENCES entity(entityid), CONSTRAINT fk_entityemployee_organizationid
FOREIGN KEY (organizationid ) REFERENCES entity(entityid)
);
Эта таблица связывает частных лиц с организациями, в которых они работают. Соответствующие функции класса DataManager аналогичны уже описанным.
Чтобы заставить систему работать, необходима еще одна последняя функция ++++++
getAllEntitiesAsObjects(), метод класса DataManager, который перечисляет все записи в базе. Этот метод завершает всю работу, которую необходимо выполнять в объектах:
Работа с UML и классами 549
public static function getAllEntitiesAsObjects() { $sql = "SELECT entityid, type from entities";
$res = mysql_query($sql, DataManager::_getConnection());
if(!$res) {
die("Невозможно получить все записи");
}
if(mysql_num_rows($res)) { $objs = array();
while($row = mysql_fetch_assoc($res)) { if($row['type'] == 'I') {
$objs[] = new Individual($row['entityid']);
}elseif ($row['type'] == 'O') {
$objs[] = new Organization($row['entityid']);
}else {
die("Неизвестный тип записи {$row['type']}!");
}
}
return $objs;
}else {
return array();
}
}
Класс DataManager позволяет перечислить все записи с контактной информаци+ ей в системе. Он проверяет значение поля ctype в таблице entity, чтобы опреде+ лить, к какому типу относится запись ++++++ частное лицо или организация, а затем созда+ ет объект соответствующего типа и добавляет его в массив, который возвращает описываемая функция.
Использование системы
К этому моменту уже становится очевидной реальная эффективность объектно+ ориентированного подхода. В результате выполнения следующего кода (test.php) отображается подробный перечень всех контактных сведений в базе данных:
<?php
require_once('class.DataManager.php'); //включаем весь
//необходимый код
function println($data) { print $data . "<br>\n";
}
$arContacts = DataManager::getAllEntitiesAsObjects(); foreach($arContacts as $objEntity) {
if(get_class($objEntity) == 'Individual') {
print "<h1>Частное лицо - {$objEntity->__toString()}</h1>";
} else {
print "<h1>Организация - {$objEntity->__toString()}</h1>";
}
if($objEntity->getNumberOfEmails()) { //Есть e-mail-адреса. Выводим заголовок
print "<h2>Email-адреса</h2>";
for($x=0; $x < $objEntity->getNumberOfEmails(); $x++) { println($objEntity->emails($x)->__toString());
}
}
if($objEntity->getNumberOfAddresses()) { //Есть почтовые адреса.
print "<h2>Адреса</h2>";
for($x=0; $x < $objEntity->getNumberOfAddresses(); $x++) {
Работа с UML и классами 551
Используя всего лишь 36 строк кода, можно отобразить почти всю информацию по записям в системе. Здесь не показаны сведения о работодателях и работниках, это зада+ ние остается в качестве упражнения читателю. Строка, в которой вызывается функция get_class, должна подсказать, какой класс используется, т.е. какая функция вызывает+ ся ++++++ getEmployer() в классе Individual или getEmployees() в классе Organization.
Резюме
UML+диаграммы ++++++ важнейший инструмент для планирования сложных (и про+ стых) приложений. Правильно сформированные диаграммы позволяют документи+ ровать сложные системы более наглядным способом, чем это можно сделать с помо+ щью текстового описания. Использование диаграмм классов как части рутинного процесса разработки программного обеспечения значительно облегчает понимание того, как должны быть организованы классы и таблицы баз данных.
Используя все преимущества OO+возможностей PHP5, можно быстро разрабаты+ вать приложения и создавать легко поддерживаемый код, который будет обладать вы+ сокой степенью гибкости и расширяемости, а также сократить общее количество ко+ да, необходимого для реализации бизнес+требований.
Разделяя программную архитектуру на уровень бизнес+логики (класс Individual) и уровень доступа к данным (класс DataManager), можно легко изменять основной источник данных, структуру таблиц и запросов, не нарушая при этом остальную часть приложения. Объекты, ответственные за реализацию бизнес+логики, не за+ громождены механизмом доступа к данным, присутствие которого может запутать и усложнить бизнес+правила.