# Организация модели в Zend Framework

Zend Framework @ 28 Август 2010

Модель определяет представление данных в вашей системе. Но это не просто база данных, а также отношения между различными кусочками информации, правила и принципы которые их объединяют в единую систему, это так называемая бизнес-логика. Модель должна быть максимально абстрагирована от конкретных технологий и реализаций (СУБД, язык программирования и фреймворков). Это в теории, на практике же приходится искать компромиссный вариант.

В этой статье я хочу расскачать об одном из простых способов реализации модели с использованимем Zend Framework, который может упростить жизнь разработчику.

ZF предоставляет программисту удобный модуль для работы с базой данных. Он включает классы Zend_Db_Table, реализующий шаблон проектирования Table Data Gateway, и Zend_Db_Table_Row, реализующий шаблон Row Data Gateway.

Zend_Db_Table представляет собой класс интерфейса таблицы базы данных, предоставляющий методы для наиболее частых операций над таблицами. Каждой таблице будет соответствовать свой класс, дочерний от Zend_Db_Table. Для каждого такого класса можно определить «собственный» класс от Zend_Db_Table_Row, объекты которого будут соответствовать отдельной строке этой таблицы.

Важно то что эти классы можно легко расширить нашей собственной логикой, необходимой для модели данных.

Классы таблиц помещаются в папку application/models/DbTable, а классы строк в папку application/models/DbRow. В стандартном автозагрузчике уже прописан путь к классам таблиц, но не к классам строк. Чтобы это исправить, добавим в Bootstrap инициализацию модифицированного автозагрузчика:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
	protected function _initAutoload() {
		// не использовать префикс модуля
		$autoloader = new Zend_Application_Module_Autoloader ( 
			array (
				'namespace' => '', 
				'basePath' => dirname ( __FILE__ )
			)
		);
		// путь к классам строк
		$autoloader->addResourceTypes( array(
			'dbrow' => array(
				'namespace' => 'Model_DbRow',
				'path'      => 'models/DbRow',
			)
		));
		return $autoloader;
	}
// ...
}

Допустим мы делаем каталог вебсайтов. У нас есть две таблицы: сайтов и их категорий. Для каждой таблицы базы данных создается класс, дочерний от Zend_Db_Table_Abstract. Например так будет выглядеть класс для таблицы сайтов:

class Model_DbTable_Sites extends Zend_Db_Table_Abstract {
	// имя таблицы базы данных
	protected $_name = 'sites';
 
	// название класса-строки
	protected $_rowClass = 'Model_DbRow_Site';
}

Этого достаточно, чтобы использовать его для работы с данными. Например вот так можно получить все записи этой таблицы:

$table = new Model_DbTable_Sites(); // создаем объект интерфейса для таблицы sites
$rows = $table->fetchAll(); // получаем набор строк - объект Zend_Db_Table_Rowset

Набор строк Rowset можно сразу передать в скрипт вида или предварительно его обработать. Важно то что все строки будут «завернуты» в объекты класса Model_DbRow_Site, который мы указали определяя класс таблицы. Объекты класса Model_DbRow_Site будут соответствовать объектам предметной области «вебсайт». Вот как он выглядит:

class Model_DbRow_Site extends Model_DbRow_Abstract {
 
	// метод используемый при приведении объекта к строке, возвращает название
	public function __toString() {
		return (string) $this->name;
	}
 
	// используется в методе toArray набора строк Rowset
	public function toArray() {
		$data = parent::toArray ();
		$data['id']          = (int) $data['id'];
		$data['id_category'] = (int) $data['id_category'];
		$data['added']       = date_create( $data['added'] )->format( DATE_W3C );
		return $data;
	}
 
	// возвращает название категории сайта
	public function getCategory() {
		if ( !empty($this->id_category) ) {
			$category = $this->findParentRow( 'Model_DbTable_Categories' );
			return ! empty($category) ? $category->name : '...';
		}
		return '';
	}

В классе Model_DbRow_Site мы расширяем стандартный класс строки логикой, специфичной только для объекта «вебсайт». Мы определили для него «магический» метод __toString, срабатывающий когда объект используется как строка, что иногда очень удобно и облегчает читабельность кода. Также переопределен метод toArray. Этод метод используется всегда, когда нужно получить данные в виде массива. Кроме того, можно добавить методы для наиболее часто используемых операций над этим типом данных, например, запрос зависимых или родительских строк. Для последнего, впрочем, необходимо прописать эти зависимости в классе таблицы:

class Model_DbTable_Sites extends Zend_Db_Table_Abstract {
	//..
 
	protected $_referenceMap = array (
		'Category' => array (
			'columns' => 'id_category', 
			'refTableClass' => 'Model_DbTable_Categories', 
			'refColumns' => 'id'
		)
	);

Кроме того, суда же можно добавить специфичную логику, необходимую при создании новых объектов-записей, например, установить текущее время в поле даты добавления:

class Model_DbTable_Sites extends Zend_Db_Table_Abstract {
	//..
 
	public function createRow( $data = array(), $defaultSource = null ) {
		$new = parent::createRow( $data, $defaultSource );
		$new->added = date_create()->format( DATE_W3C );
		return $new;
	}

А также облегчить поиск записей по запросам:

class Model_DbTable_Sites extends Zend_Db_Table_Abstract {
	//..
 
	public function getByUrl( $url ) {
		// Извлекаем имя хоста из URL
		list($url) = explode('?', $url);
		preg_match( '/^(http:\/\/)?(www\.)?([^\/]+)/i', $url, $matches);
		if ( empty( $matches[3] ) )
			throw new Model_DbRow_Exception( 'Wrong URL');
		$host = $matches[3];
		$site = $this->fetchRow( $this->select()
			->where( 'url LIKE ?', "%$host%" )
		);
		return $site;
	}

Leave a Reply