Главная > web, Программирование > PHP: Пример использования ORM Doctrine.

PHP: Пример использования ORM Doctrine.

В данном посте рассмотрим простенький пример использования ORM, а именно, Doctrine. Если кто не в курсе, то ORM (Object-Relational Mapping, объектно-реляционная проекция) - такая штука, которая обеспечивают классам прозрачный доступ к базе данных. Правда не всяким классам, а тем, которые представляют описание нашей модели данных. В общем мне бы пару лет назад узнать о такой штуке, может быть я и не забросил изучение php, и вообще, много чего полезного сделал :D. Если кто-то знаком с паттернами проектирования, то можно сказать, что Doctrine соответствует шаблону Active Record. К своему стыду, сам я не знаком с ними, поэтому ничего конкретней сказать не могу пока что.

Кстати, мы уже затрагивали мельком тему ORM, но только для python. Можете посмотреть про SQLAlchemy тут.

Далее будет рассмотрен пример написания модуля на php с использованием ORM Doctrine для отправки личных сообщений между пользователей.

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

  • id - id сообщения;
  • uid_from - id отправившего пользователя;
  • uid_to - id получателя;
  • title - заголовок сообщения;
  • text - текст сообщения;
  • flag_unread - флаг нового сообщения;
  • flag_del_from - признак удалённого сообщения из исходящих;
  • flag_del_to - признак удалённого сообщения из входящих;
  • date - дата отправки в формате unix timestamp;

На этом этап проектирования у меня закончился. Теперь перейдём к технической стороне вопроса.

Качаем тот самый Doctrine, находим в архиве директорию, где лежит файл Doctrine.php и копируем всё содержимое этого каталога в отдельную папку в нашем проекте (я скопировал в doctrine). Следуя документации, создаём файл bootstrap.php в корне проекта с таким содержанием:

<?php
require_once(dirname(__FILE__) . '/doctrine/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE);
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
$manager->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);

$user = 'tst';
$password = 'tst';
$host = 'localhost';
$db = 'tst';
$dsn = "mysql://$user:$password@$host/$db";

$conn = Doctrine_Manager::connection($dsn);

Doctrine::loadModels('models');

?>

Тут настраивается подключение к БД и некоторые параметры ("ленивый" способ загрузки моделей, валидация данных и т.п.). Последняя строчка указывает Doctrine где искать наши модели.

Теперь в корне проекта создаём папку models и в ней файл PrivateMessage.php. Учтите, что имя файла и класса, который описан в нём, должны совпадать. Вот как у меня выглядит этот файл:

<?php

class PrivateMessage extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('uid_from', 'integer', 8, array(
'unsigned' => true,
'notnull' => true
)
);
$this->hasColumn('uid_to', 'integer', 8, array(
'unsigned' => true,
'notnull' => true
)
);
$this->hasColumn('title', 'string', 128);
$this->hasColumn('text', 'string', 255);
$this->hasColumn('flag_unread', 'boolean', array(
'default' => false,
'notnull' => true
)
);
$this->hasColumn('flag_del_from', 'boolean', array(
'notnull' => true,
'default' => false
)
);
$this->hasColumn('flag_del_to', 'boolean', array(
'notnull' => true,
'default' => false
)
);
$this->hasColumn('date', 'integer', 8, array(
'notnull' => true,
'default' => time()
)
);
}
}

?>

Тут мы не описали поле id, т.к. Doctrine создаст его автоматически, если не указано других первичных ключей (primary key). Так же в таблице не указаны индексов, что не есть хорошо. Неплохо было бы сделать их для полей uid_from и uid_to хотя бы. Но не сделаем, ибо лень, как-нибудь в другой раз ;-).

Следующим шагом создаём в корне проекта файл test.php, в котором будут описаны основные функции для работы с личными сообщениями и некоторый код для демонстрации функционала. Вот содержимое этого файла:

<?php
require_once('bootstrap.php');

$conn->export->exportClasses(array('PrivateMessage'));

#FIXME: Set current user ID from session.
$curr_uid = 1;

function get_messages($inbox, $uid) {
/*
* If $inbox == true then returns inbox of user $uid,
* else returns outbox.
*/

if($inbox) {
$q = 'pm.uid_to = ? AND pm.flag_del_to = ?';
} else {
$q = 'pm.uid_from = ? AND pm.flag_del_from = ?';
}

return Doctrine_Query::create()
->from('PrivateMessage pm')
->where($q, array($uid, 0))
->fetchArray();
}

function get_message($id, $uid) {
$q = Doctrine_Query::create()
->from('PrivateMessage pm')
->where('pm.id = ? AND (pm.uid_from = ? OR pm.uid_to = ?)', array($id, $uid, $uid));
$q = $q->fetchArray();
if(count($q)) {
if($uid == $q[0]['uid_to'] && $q[0]['flag_unread']) {
Doctrine_Query::create()
->update('PrivateMessage pm')
->set('pm.flag_unread', '?', false)
->where('pm.id = ?', $q[0]['id'])
->execute();
}
return $q[0];
} else {
return false;
}
}

function delete_messages($ids, $uid) {
/*
* $ids - array of ids of messages, that will be deleted.
* $uid - current user id.
* P.S. Broken English - is the most popular language in the world ;-).
*/
foreach($ids as $id) {
Doctrine_Query::create()
->update('PrivateMessage pm')
->set('pm.flag_del_to', '?', true)
->where('pm.id = ? AND pm.uid_to = ?', array($id, $uid))
->execute();

Doctrine_Query::create()
->update('PrivateMessage pm')
->set('pm.flag_del_from', '?', true)
->where('pm.id = ? AND pm.uid_from = ?', array($id, $uid))
->execute();
}
Doctrine_Query::create()
->delete('PrivateMessage pm')
->where('pm.flag_del_from = 1 AND pm.flag_del_to = 1')
->execute();
}

function send_message($to, $title, $text, $uid) {
$pm = new PrivateMessage();
$pm->uid_from = $uid;
$pm->uid_to = $to;
$pm->title = $title;
$pm->text = $text;
$pm->flag_unread = true;
// Если хранить в исходящих ненадо,
// то можно установить следующий флаг в true.
$pm->flag_del_from = false;
$pm->flag_del_to = false;
$pm->save();
}

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru" xml:lang="ru">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<?php

echo 'Current user:' . $curr_uid;

// Send message section.
if((int)@$_REQUEST['to'] && !@empty($_REQUEST['title']) && !@empty($_REQUEST['text'])) {
send_message((int)$_REQUEST['to'], $_REQUEST['title'], $_REQUEST['text'], $curr_uid);
}
// End of send message section.

// Delete messages section.
if(@count($_REQUEST['del_messages'])) {
echo '<pre>';
delete_messages($_REQUEST['del_messages'], $curr_uid);
echo '</pre>';
}
// End of delete messages section.

// Inbox section.
echo '<hr/>Inbox:';
echo '<form name="del_messages" action="" method="post">';
echo '<table border="1"><tr><td>Del</td><td>Message ID</td><td>Date</td><td>From</td><td>Title</td></tr>';
$m = get_messages(true, $curr_uid);
foreach($m as $message) {
// Mark as bold unread messages.
if($message['flag_unread']) {
$b_open = '<b>';
$b_close = '</b>';
} else {
$b_open = '';
$b_close = '';
}
// End mark.
echo "<tr><td><input type=\"checkbox\" name=\"del_messages[]\" value=\"$message[id]\"/></td><td><a href=\"?message_id=$message[id]\">$b_open$message[id]$b_close</a></td><td>$b_open" . date('d.m.y H:i', $message['date']) . "$b_close</td><td>$b_open$message[uid_from]$b_close</td><td>$b_open$message[title]$b_close</td></tr>";
}
echo '<tr><td colspan="5"><input type="submit" value="Delete selected"/></td></tr>';
echo '</table>';
// End of inbox section.

// Outbox section.
echo '<hr/>Outbox:';
echo '<table border="1"><tr><td>Del</td><td>Message ID</td><td>Date</td><td>To</td><td>Title</td></tr>';
$m = get_messages(false, $curr_uid);
foreach($m as $message) {
// Mark as bold unread messages.
if($message['flag_unread']) {
$b_open = '<b>';
$b_close = '</b>';
} else {
$b_open = '';
$b_close = '';
}
// End mark.
echo "<tr><td><input type=\"checkbox\" name=\"del_messages[]\" value=\"$message[id]\"/></td><td><a href=\"?message_id=$message[id]\">$b_open$message[id]$b_close</a></td><td>$b_open" . date('d.m.y H:i', $message['date']) . "$b_close</td><td>$b_open$message[uid_to]$b_close</td><td>$b_open$message[title]$b_close</td></tr>";
}
echo '<tr><td colspan="5"><input type="submit" value="Delete selected"/></td></tr>';
echo '</form>';
echo '</table>';
// End of outbox section.

// View message section.
if((int)@$_REQUEST['message_id']) {
$m = get_message($_REQUEST['message_id'], $curr_uid);
if($m !== false) {
echo "<hr/>View message #$m[id]:";
echo '<table border="1">';
echo "<tr><td>From:</td><td>$m[uid_from]</td></tr>";
echo "<tr><td>To:</td><td>$m[uid_to]</td></tr>";
echo "<tr><td>Title:</td><td>$m[title]</td></tr>";
echo "<tr><td>Message:</td><td>$m[text]</td></tr>";
echo '</table>';
}
}
// End of view message section.

// Send message section.
echo '<hr/>New message:';
echo '<table border="1">';
echo '<form action="" method="post" name="send_message">';
echo "<tr><td>From:</td><td>$curr_uid</td></tr>";
echo '<tr><td>To:</td><td><input name="to"/></td></tr>';
echo '<tr><td>Title:</td><td><input name="title"/></td></tr>';
echo '<tr><td>Message:</td><td><textarea name="text"></textarea></td></tr>';
echo '<tr><td colspan="2"><input type="submit" value="send message"></td></tr>';
echo '</form>';
echo '</table>';
// End of send message section.

?>
</body>
</html>

$conn->export->exportClasses(array('PrivateMessage')) - это строка создаст таблицу private_messaages, если таковой ещё нет. Так что можно её перенести в установочный скрипт, но поскольку у нас нет такого, то мы её оставим.

$curr_uid - эта переменная по идее должна браться из сессии. Как она должна называться в сессии я не знал, поэтому присвоил ей значение в коде 1.

get_messages($inbox, $uid) - возвращает все входящие сообщения пользователя $uid, если $inbox равна true, иначе - исходящие. Неплохо было бы добавить лимит на выборку записей (например, с 1й по 20ю, с 21й по 40ю), но поскольку этот модуль нигде применять не планирую, то и доделывать его нет желания. Если кому то надо - пишите в комменты ;-).

get_message($id, $uid) - возвращает сообщение $id, $uid передаётся во все функции для проверки принадлежности сообщений текущему пользователю. Ещё раз повторю, идентификатор ползьователя должен браться из сессии.

delete_messages($ids, $uid) - $ids - массив id'шников сообщений, которые надо удалить. Реально удаляются записи из БД только те, у которых flag_del_from и flag_del_to равны 0.

send_message($to, $title, $text, $uid) - $to - id получателя, $uid - отправителя, остально думаю понятно.

Дальше по коду идёт часть с html-выводом и обработкой переданных парамтеров, так что ничего интересного. Код по моему везде достаточно просто, так что подробно объяснять не стал. Если вопросы остались - в комменты.

P.S. Это моя первая попытка сделать что-либо с использованием Doctrine.

Пожалуйста, оцените полезность и качество данной статьи. Одна звезда - плохо, 5 - хорошо.
1/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.2/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.3/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.4/5.5/5. (12 голосов, средний: 3,67 из 5)
Загрузка...
  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.