Блог о программировании

Реализация ORM в PHP на примере ActiveRecord

 3 ноября 2015 г. 20:52

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 < 0 || $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 < 0 || $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-технологии.

Поделиться статьей

Оставить комментарий