Шаблон проектирования «Active record» представляет собой популярный подход к хранению данных в реляционных базах данных. Впервые упоминается Мартином Фолером (Martin Fowler) в книге «Patterns of Enterprise Application Architecture». Суть шаблона в том, что запись в базе данных представлена как объект (класс) в языке программирования. Обращение к базе данных происходит через методы класса, а не посредством SQL. Изменения свойств объекта ведут к изменению значений полей в базе данных.
Существует множество реализация данного подхода, все они имеют свои плюсы и минусы. Я хочу рассказать о немного видоизмененном шаблоне. Я бы назвал его «Active Record Template». Суть его в том, чтобы задать логику поведения записи в базе данных. Но не конкретной, а «в общем виде». Когда требуется несколько однотипных таблиц (и соответственно данных), но по какой-то причиной данные нельзя хранить в одной таблице.
Обо всем по порядку. Сначала о моем «Active Record»:
Класс является реализацией шаблона проектирования Active Record если:
конструктор имеет вид __construct($id)
существует метод createSelf($arg1, $arg2, $arg3...), который создает новую запись в БД и новый объект
существует метод selfDelete() который удаляет запись в БД и объект
публичный член $id
все члены, отражающие поля в базе данных декларированы как protected и доступ к ним осуществляется только через методы
все операции непосредственно с БД вынесены в отдельные методы с префиксом db..()
Лучше всего это показать на примере. Вот класс Image:
class Image
{
public $id;
protected $filename;
protected $owner;
protected $size;
function __construct($id)
{
assert('is_numeric($id)');
$this->id = $id;
$this->dbReadImage();
}
public function changeOwner($newOwner)
{
$this->owner = $newOwner;
$this->dbUpdateOwner();
}
public function getFilename()
{
return $this->filename;
}
// управление записью в БД
public static function createImage($filename, $owner)
{
$size = filesize($filename);
$q = 'insert into image
set filename=$filename,
owner=$owner,
size=$size'
return new self($lastInsertId);
}
public static function selfDelete()
{
$q = 'delete from images where id=$this->id';
unset($this);
}
// методы для работы с БД
protected function dbReadImage()
{
$q = 'select filename, owner, size from images where id=$this->id';
// заполняем свойства из БД
}
protected function dbUpdateOwner()
{
$q = 'update images set owner=$this->owner where id=$this->id';
}
}
вот так это работает из программы:
$image = Image::createImage('/home/user/file', 'user');
$image->changeOwner('user_other');
$image->selfDelete();
красота :)
Теперь расскажу об Active Record Template. Лучше, наверное, на примере:
Тэги. Атрибут пресловутого Вэб2.0 встречается довольно часто в проектах. Буду использовать упрощенный вариант для наглядности:
class ObjectTags
{
protected $dbTableName;
protected $relatedObjectId;
function __construct( $relatedObjectId, $dbTableName=null )
{
assert('is_numeric($relatedObjectId)');
if( !is_null($dbTableName) )
{
$this->dbTableName = $dbTableName;
}
$this->relatedObjectId = $relatedObjectId;
$this->dbReadTags();
}
public function addTag($tag)
{
$this->tags[] = $tag;
$this->dbAddTag($tag);
}
public function delTag( $tag )
{
$key = array_search($tag, $this->tags);
if ($key !== false)
{
unset($this->tags[$key]);
}
$this->dbDelTag($tag);
}
private function dbReadTags()
{
$q = "
select
{$this->dbTableName}.tag as tag
from {$this->dbTableName}
where
object_id = {$this->relatedObjectId}
";
...
}
private function dbAddTag($tag)
{
$q = "
insert into {$this->dbTableName}
set
object_id = {$this->relatedObjectId},
tag = '{$tag}'
";
}
private function dbDelTag( $tag )
{
$q = "
delete from {$this->dbTableName}
where
object_id = {$this->relatedObjectId}
and
tag = '{$tag}'
";
}
}
Теперь для конкретной ситуации, например если нужно использовать тэги для картинок:
создаем таблицу в БД
CREATE TABLE `image_tags` (
`object_id` BIGINT UNSIGNED NOT NULL ,
`tag` VARCHAR( 100 ) NOT NULL
);
где object_id - это внешний ключ, ID картинки в таблице с картинками, например images.id
и класс:
class ImageTags extends ObjectTags
{
protected $dbTableName = 'image_tags';
}
и можно пользоваться:
$image = new Image(34856); // image_id=34856
$imageTags = new ImageTags($image->id);
NewsTags, UserTags и т.п. Делается аналогично - создать таблицу и новый класс в 10 строк. Никакой путаницы и отличные возможности для расширения.
Сразу хочу сказать, я против полной абстракции обращений к БД. Речь идет именно о типовых случаях. Если Active Record Template начинает двигаться в сторону Database Abstraction (появляются параметры запросов, сортировки, название полей) - это нужно прекращать и писать новый класс под конкретную проблему.