Active Record/Template

Jun 18, 2008 11:20


Шаблон проектирования «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 (появляются параметры запросов, сортировки, название полей) - это нужно прекращать и писать новый класс под конкретную проблему.

db, templates, php

Previous post Next post
Up