Реализация ORM в PHP на примере ActiveRecord
ORM(object-relational mapping) переводится как объектно-реляционное отображение. Наверное, самой распространенной парадигмой разработки ПО является парадигма объектно-ориентированного программирования. В ней все объекты реального мира представляются аналогичными объектами в коде с тем-же или похожим набором характеристик. Программистам, пишущим программы в ООП-парадигме, непривычно писать во многих частях SQL-запросы к БД для выполнения каких-либо действий. Эти действия должны быть инкапсулированы в определенном классе или наборе классов, которые бы взаимодействовали с БД и скрывали ее структуру.
Одной из реализаций технологии ORM является шаблон проектирования ActiveRecord, описанный Мартином Файлером в своей книге "Шаблоны архитектуры корпоративных приложений". Класс, реализующий данный шаблон, должен удовлетворять некоторым требованиям:
- класс представляет собой отображение таблицы из БД;
- каждый экземпляр класса представляет собой строку в отображаемой таблице;
- код взаимодействует с отображаемой таблицей исключительно через реализованный класс.
Думаю, будет понятнее на наглядном примере.
Допустим, у нас есть в БД таблица products такой структуры:
| Имя поля | Размерность | Обязательное | Описание поля |
| id | int(11) | Да(Primary Key) | Идентификатор |
| title | varchar(255) | Да | Наименование |
| price | int(11) | Да | Цена |
| discount | int(11) | Да | Скидка |
| description | text | Нет | Описание/пояснение |
И для этой таблицы нам надо реализовать класс ActiveRecord. Для начала, создадим класс Product и добавим в него все поля из таблицы products:
class Product {
private $id;
private $title;
private $price;
private $discount;
private $description;
}Далее, добавим геттеры и сеттеры для полей. Для поля $id будет только геттер, поскольку значение этого поля устанавливается автоматически при вставке новой строки в БД. Конструктор объявим закрытым. Тогда для создания новых пустых экземпляров будет использовать статичесий метод newEmpltyInstnace :
class Product {
public static function newEmptyInstance() {
return new self();
}
private $id;
private $title;
private $price;
private $discount;
private $description;
private function __construct() {
}
public function getId() {
return $this->id;
}
public function setTitle($aTitle) {
$this->title = $aTitle;
}
public function getTitle() {
return $this->title;
}
public function setPrice($aPrice) {
$this->price = $aPrice;
}
public function getPrice() {
return $this->price;
}
public function setDiscount($aDiscount) {
$this->discount = $aDiscount;
}
public function getDiscount() {
return $this->discount;
}
public function setDescription($aDescription) {
$this->description = $aDescription;
}
public function getDescription() {
return $this->description;
}
}Так, теперь мы можем создавать новый пустой экземпляр объекта и присваивать его полям значения. Нужно добавить метод сохранения значений в БД. Поскольку я лишь показываю пример реализации ActiveRecord, для работы с БД буду использовать функции типа mysql_query, хотя в реальности должен быть реализован специальный класс для взаимодейсвитя с БД. Поэтому, соединение с БД пока пропустим. Итак, код сохранения:
public function save() {
if (isset($this->id)) {
$this->_update();
} else {
$this->_insert();
}
}
private function _update() {
mysql_query("UPDATE `products` SET `title`='{$this->title}', "
. "`price`='{$this->price}', `discount`='{$this->discount}', "
. "`description`='{$this->description}' WHERE `id`={$this->id}");
}
private function _insert() {
mysql_query("INSERT INTO `products` (`title`, `price`, `discount`, `description`)"
. " VALUES ('{$this->title}', '{$this->price}', '{$this->discount}', '{$this->description}')");
$new_id = mysql_insert_id();
$this->id = $new_id;
}При вызове метода save(), определим, есть ли эта запись в БД. Если есть, то у экземпляра уже установлено поле $id, и все остальные поля в БД будут обновлены; если же поля $id нет, то в БД добавляется новая запись и идентификатор из БД присваивается полю $id экземпляра класса.
Итак, с помощью нашего класса можно создавать новые поля в БД и обновлять их. Но как же насчет удаления? Для этого создадим статический метод delete :
public static function delete($aId) {
$lId = (int) $aId;
if ($lId PHP_INT_MAX) {
return false;
}
$result = mysql_query("DELETE FROM `products` WHERE `id`=" . $lId);
return $result;
}Осталось написать несколько вспомогательных статических методов для удобства поиска в нашей таблице products :
public static function find($aCount, $aOptFrom = null) {
$lFrom = is_null($aOptFrom) ? '' : (int) $aOptFrom . ', ';
$query = "SELECT `id` FROM `products` LIMIT {$lFrom}{$aCount}";
$result = mysql_query($query);
if ($result !== false) {
$lReturnProducts = array();
while ($row = mysql_fetch_array($result)) {
$lReturnProducts[] = self::newInstance($row['id']);
}
return $lReturnProducts;
} else {
return false;
}
}
public static function count() {
$result = mysql_query("SELECT COUNT(`id`) as `count` FROM `products`");
$row = mysql_fetch_array($result);
$count = (int) $row['count'];
return $count;
}
public static function newInstance($aId) {
$lId = (int) $aId;
if ($lId PHP_INT_MAX) {
return false;
}
$result = mysql_query("SELECT * FROM `products` WHERE `id`=" . $lId . " LIMIT 1");
if ($result !== false) {
$row = mysql_fetch_array($result);
$product = new self();
$product->id = $row['id'];
$product->title = $row['title'];
$product->price = $row['price'];
$product->discount = $row['discount'];
$product->description = $row['description'];
return $product;
} else {
return false;
}
}Метод find() возвращает массив экземпляров класса, которые соответствуют поисквому запросу для таблицы products, метод count() возвращает количество записей в таблице, ну а метод newInstance() возвращает единичный экземпляр класса.
Все! Теперь у нас есть весь набор средств для манипуляции таблицей products, используя класс Product. Поля класса объявлены сокрытыми для того, чтобы нельзя было напрямую устанавливать значения, поскольку в сеттерах полей мы можем проверять устанавливаемые значения в соответствии в типом данных, указанным в БД для исключения ошибок.
Реализованный класс, страницу php, на которой используются возможности класса, а также sql-файл для импорта в БД с таблицей products и ее наполнением, можно скачать с github
Подведем итог:
ORM придумано для удобной работы с БД в терминах ООП.
Шаблон проектирования ActiveRecord является концепцией ORM-технологии.
Comments