Итак, как у меня используютя классы.
Я не буду описывать работу с БД. У меня пока используется adodb (статья по нему есть на detail.phpclub.ru).
Также я пропущу работу с классом шаблонизатора (pear::html::teamplate::sigma) - работа с ним принципиально ничем не отличается от работы с другими шаблонизаторами.
Первое о чем я упомяну - это регистр объектов.
Изнутри, регистр объектов (ObjectRegistry) представляет собой массив, в котором хранятся ссылки
на объекты.
Зачем это нужно ? Допустим у нас класс, которому нужен доступ к классу базы данных и шаблонизатору.
Если не использовать global (а я его принципиальноне использую), то эти объекты
можно получить лишь передав их через конструктор или соответствующий метод.
$object = new SomeObject($conn, $tmpl);
Если допустим появятся еще и настройки (почему у меня настройки в своем классе объяснять не хочу - это необязательное решение)
то надо будет
1. переписать класс, чтобы он принимал 3 параметра
2. переписать код в котром используется класс и добавить еще один параметр:
$object = new SomeObject($conn, $tmpl, $settings);
Мало того, что мы кучу вего написали, так еще и конструктор стал получать кучу параметров
В случае с ObjectRegistry у меня всегда вызов конструктора выглядит :
$object = new SomeObject($registry);
Хотя основная его цель - позволить объектам взаимодействовать друг с другом.
Теперь переходим к основной части.
Допустим на сайте есть Новости, Регистрация, Статьи.
То есть на сайте есть такие сущности как новость, пользователь и статья
У этих 3-х сущностей есть общее поведение:
- добавление (проверяем входные данные, если все Ок, генерируем INSERT-запрос и выполняем его)
- уделаение (тут все ясно)
- редактирование (очень похоже на добавление только UPDATE-запрос генерируется)
Но есть и различия. Например у разных сущностей разные проверки.
Также некоторые сущности имеют еще и дополнительное поведение (например у юзера есть авторизация, "Вспомнить пароль")
У меня есть абстрактный класс DBObject.
В его задачу входит работа с сущностями - проверка/добавление/редактирование/удаление и т.д.
Рассмотрим добавление сущности в таблицу.
Здесь можно выделить такие этапы:
1. Получение данных из POST-запроса
2. Проверка данных
3. Подготовка данных к добавлению
Например при регистрации юзера надо пароль зашифровать.
Также здесь к данным добавляется время добавления (если в таблице есть такое поле).
Также на этом этапе при регистрации юзера генерируется случайное число, которое шлется ему на e-mail для подтверждения регистрации
Вобщем здесь генерируются некоторые значения для полей, которых нет в реальной форме добавления.
4. Выполняем INSERT-запрос и получаем идентификатор новой записи
5. Еще что-нибудь
Например если юзер закачивал картинку, то она из временной папки копируется в папку с картинками и получает имя
$user_id.jpeg
Также здесь можно выполнить некоторые другие SQL-запросы, если например сущность
хранится в нескольких таблицах. Например юзер указал свои интересы и здесь они записываются в отдельную таблицу (связь "многие-ко-многим")
Метод add() класса DBObjects выполняет вставку записи. Выглядит он так (упрощенно) :
function add() {
$this->loadFromPost(); // загружаем данные из ПОСТ
$this->checkData(\'add\'); // проверяем
if (sizeof($this->errors) == 0) { // если нет ошибок
// if data OK
$this->prepareData(\'add\'); // подготавливаем данные в вставке
// генерируем INSERT-запрос (это фича adodb)
$res = $this->conn->Execute("SELECT * FROM ".$this->table." WHERE ".$this->id_name." = -1");
$sql = $this->conn->GetInsertSQL($res, $this->fields);
// запрос сгенерировали, теперь выполняем
$this->conn->Execute($sql);
$this->id = $this->conn->Insert_ID();
$this->onAdd(); // вызывается после вставки данных
return $this->id;
} else {
return false;
}
}
я убрал некоторые проверки, чтобы код был более понятным.
У трех упомянутых сущностей только 3 метода (используемые в этом коде) могут
различаться : $this->onAdd(), $this->prepareData(), $this->checkData()
Именно эти методы надо будет переопределить в наследниках (можно и не переопределять если они не нужны)
Класс DBObject имеет такие методы (только основные)
setID($id) - получает ИД объекта, делает запрос к БД и получает данные о сущности
add() - описан выше
edit() - редактирование объекта
delete() - удаление объекта
Эти объекты переопределять не надо
onAdd(), onEdit(), onDelete() - эти объекты можно переопределить
checkData() - проверка данных (тоже переопределяется)
prepareData() - подготовка данных (тоже переопределяется)
--------
Как все это работает.
Делаем наследника (для этого есть генератор), ручкам пишем методы
checkData(), prepareData() + события on*() а в скриптах код выглядит :
добавление
$news = new News($reg); // $reg == ObjectRegistry
if ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {
if ($news->add()) {
// новость добавлена
_header(\'news.php?\'.SID);
exit;
} else {
// произошла ошибка
$errors = $news->errors;
$tpl->setVariable(er_convert($errors)); // передаем ошибку в шаблон
}
}
/// тут работа с шаблонизатором
редактирование
$cat = new Category($reg);
// идентификатор категории, которую редактируем
$cat_id = max(0, intval($_REQUEST[\'cat_id\']));
if ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {
$cat->setID($cat_id);
if ($cat->edit()) {
// если ошибок нет
_header("cats.php?".SID);
exit;
} else {
$errors = $cat->errors;
// передаем ошибки шаблону
$tpl->setVariable(er_convert($errors));
// передаем введенные данные шаблону
$tpl->setVariables($cat->getFields()) //
}
} else {
$cat->setID($cat_id);
// передаем шаблону данные, которые надо отредактировать
$tpl->setVariables($cat->getFields()) //
}
Удаление
$cat_id = max(0, intval($_GET[\'cat_id\']));
$cat = new Category($reg);
$cat->setID($cat_id);
$cat->delete();
_header(\'cats.php?\'.SID);
Если используемые методы не предоставляют нужную мне функциональность, то
я могу дописать метод или реализовать его без класса (я на классах не помешан)
Позволяет ли этот подход повторно использовать код ?
Да позволяет.
Написал класс User. Сделал наследника, чуть модфицировал - получил класс Admin.
Сделал класс для управления файлами (с upload-ом), сделал наследника, чуть модифицировал - получил класс для управления только изображениями.
Пример, как создается наследник.
Сначало создаем ini-файл. Примерно такого типа
[db]
table = tgp_links # название таблицы
id_name = link_id # имя первичного ключа
[class]
name = Link # имя класса
extends = DBObject # имя родительского класса
[fields] # это поля и фильтры для них
cat_id = intval
url =
title = htmlspecialchars
description = "htmlspecialchars|nl2br"
По этому ini-файлу генератор генерирует класс.
Дальше ручками надо написать нужные методы:
// проверка просто для примера
function checkData($mode=\'add\') {
if (empty($this->fields[\'title\'])) {
$this->errors[\'title\'] = _("Title is empty");
}
if (empty($this->fields[\'description\'])) {
$this->errors[\'description\'] = _("Description is empty");
}
if (empty($this->fields[\'url\'])) {
$this->errors[\'url\'] = _("URL is empty");
}
}
function prepareData($mode = \'add\') {
if ($mode == \'add\') { // в массив данных (которые будут вставлены в таблицу)
// добавляем время записи.
$this->fields[\'date\'] = time();
}
}
создав эти 2 метода класс полностью готов к работе.
Правда еще шаблоны сверсать надо, но это уже другая история.
Есть еще класс для управления списком таких объектов, но мне лень про него писать.
Он используется при выводе списка объектов, постраничной выборке, изменения сортировки.
Одно из преимуществ того, что классы всех сущностей похожи, в том, что очень
много кода можно генерировать (не толлько классы, но и код)
По поводу объемов кода
DBObject + DBObjectList = 15 кб
Наследники по разному = от 1 - 17 кб (в среднем около 7 кб)
Все дальше писать лень.