Реализация шаблона Data Mapper

Приложив усилия и применив знания в программировании, можно создать один класс Mapper, предназначенный для обслуживания нескольких объектов. Однако обычно приходится наблюдать отдельный класс Mapper, обслуживающий главный класс модели приложения.
На рисунку показаны три конкретных класса Mapper и абстрактный суперкласс.
На самом деле, поскольку объекты Space фактически являются подчиненными для объектов venue, то можно выполнить рефакторинг класса SpaceMapper в venueMapper. Однако в данных упражнениях я буду использовать их по отдельности.

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

Наш базовый класс обычно выполняет вспомогательные операции до или после основной операции, поэтому шаблон Template Method используется для явного делегирования (вызовы от конкретных методов, таких как insert ( ), к абстрактным, таким как doInsert (), и т.д.) Как вы увидите ниже, реализация определяет, какие из методов базового класса сделаны конкретными таким способом.
Scr12 Реализация шаблона Data MapperВот упрощенная версия базового класса Mapper.

abstract class woo_mapper_Mapper {

protected static $PDO;

function _construct() {

if ( ! isset(self::$PDO) ) {

$dsn = woo_base_ApplicationRegistry::getDSN( );

if ( is_null( $dsn ) ) {

throw new woo base AppException( «DSN не определен» );

}

self::$PDO = new PDO( $dsn );

self::$PDO->setAttribute(PDO::ATTR_ERRMODE,

PDO::ERRMODE EXCEPTION); }

}

}

function find( $id ) {

$this->selectStmt()->execute( array( $id ) );

$array = $this->selectStmt()->fetch( );

$this->selectStmt()->closeCursor( );

if ( ! is_array( $array ) ) { return null; }

if ( ! isset( $array['id'] ) ) { return null; }

$object = $this->createObject( $array );

return $object;

}

function createObject( $array ) {

$obj = $this->doCreateObject( $array );

return $obj;

}

function insert( woo_domain_DomainObject $obj ) {

$this->doInsert ( $obj ) ,-

}

abstract function update( woo_domain_DomainObject $object );

protected abstract function doCreateObject( array $array );

protected abstract function doInsert( woo_domain_DomainObject $object );

protected abstract function targetClass();

protected abstract function selectStmt();

}

В методе конструктора используется класс ApplicationRegistry для получения DSN, который будет использоваться с расширением PDO. Для классов, подобных этому, желаемую информацию можно получить с помощью отдельного сингл тона или реестра запросов. Не всегда существует удобный путь от уровня управления к объекту Mapper, по которому можно передать данные. Еще один способ создания объекта Mapper — передать его самому классу Registry. Вместо того чтобы создавать его экземпляр, объекту Mapper можно предоставить объект PDO в качестве аргумента конструктора.

abstract class woo_mapper_Mapper {

protected $PDO;

function _construct( PDO $pdo ) {

$this->pdo = $pdo;

}

}

Клиентский код получает новый объект VenueMapper от объекта Registry с помощью вызова woo_base_RequestRegistry:: getVenueMapper (). При этом создается экземпляр объекта Mapper, генерирующий также объект PDO. Для последующих запросов этот метод будет возвращать сохраненную в кеше ссылку на объект Mapper. Компромисс состоит в том, что вы наделяете объект Registry намного большей информацией о вашей системе, но объекты Mapper остаются в неведении относительно глобальных данных конфигурации.

Метод insert () не делает ничего, кроме делегирования полномочий методу dolnsert (). В результате я вынес нечто из абстрактного метода insert (), поскольку знаю, что эта реализация будет сделана в свое время.

Метод f ind () отвечает за вызов подготовленного оператора (предоставленного реализующим дочерним классом) и получение необработанных данных. Он заканчивает работу, вызывая метод createObject () . Конечно, детали преобразования массива в объект будут меняться от случая к случаю, поэтому эти детали будут обрабатываться абстрактным методом doCreateObject (). И снова, кажется, что метод createObject () не делает ничего, кроме делегирования полномочий дочерней реализации, и снова мы скоро добавим вспомогательные операции, которые сделают использование шаблона Template Method стоящим затраченных усилий.

В дочерних классах будут также реализованы специальные методы нахождения данных в соответствии с заданными критериями (например, нам нужно будет найти объекты Space, которые принадлежат объектам Venue).

Давайте посмотрим на этот процесс с точки зрения дочернего объекта, class woo_mapper_VenueMapper extends woo_mapper_Mapper {

function __construct() {

parent::_construct();

$this->selectStmt = self::$PDO->prepare(

«SELECT * FROM venue WHERE id=?»);

$this->updateStmt = self::$PDO->prepare(

«UPDATE venue SET name=?, id=? WHERE id=?»);

$this->insertStmt = self::$PDO->prepare(

«INSERT into venue ( name ) values( ? )»);

}

protected function doCreateObject( array $array ) {

$ob j = new woo_domain_Venue ( $array ['id' ] ) ;

$obj->setname( $array['name'] );

return $obj;

}

protected function dolnsert( woo_domain_DomainObject $object ) {

$values = array( $object->getname() );

$this->insertStmt->execute( $values );

$id = self::$PDO->lastInsertId() ;

$object->setId( $id );

}

function update( woo_domain_DomainObject $object ) {

$values = array( $object->getname(),

$object->getid(), $object->getId() );

$this->updateStmt->execute( $values );

}

function selectStmt() {

return $this->selectStmt;

}

protected function targetClass() {

return «woo_domain_Venue» ;

}

}

И снова в этом классе отсутствуют некоторые нужные нам вещи. Тем не менее он делает свое дело. В конструкторе подготавливаются некоторые SQL-операторы для последующего использования. Их можно сделать статическими и совместно использовать во всех экземплярах класса venueMapper или, как было описано ранее, можно сохранить один объект Mapper в Registry, тем самым избавившись от повторного создания экземпляров. Но этот рефакторинг я оставляю вам!

В классе Mapper реализован метод find (), который вызывает selectStmt (), чтобы получить подготовленный оператор SELECT. Предполагая, что все идет хорошо, Mapper вызывает метод VenueMapper : : doCreateObject ( ). Здесь мы используем ассоциативный массив для генерации объекта Venue.

С точки зрения клиента, этот процесс — сама простота.

$mapper = new woo_mapper_VenueMapper();

$venue = $mapper->find( 12 );

print_r( $venue );

Метод print_r() — это быстрый способ подтверждения того, что find () успешно выполнил свою работу. В моей системе (где существует строка в таблице venue с идентификатором ID 12) этот фрагмент кода выводит следующее.

woo_domain_Venue Object

(

[name :woo_domain_Venue:private] => The Eyeball Inn [spaces:woo_domain_Venue:private] =>

[id:woo_domain_DomainObject:private] => 12

)

Методы doinsert ( ) и update ( ) реверсируют процесс, установленный методом find ( ). Каждому из них передается объект DomainObject, из которого затем извлекаются необработанные данные и вызывается метод PDOStatement : : execute ( ) для получения результирующей информации. Обратите внимание, что в методе doinsert ( ) запоминается идентификатор ID предоставленного объекта. Не забывайте, что объекты в PHP передаются по ссылке, поэтому это изменение отобразится и в клиентском коде по имеющейся в нем ссылке.

Еще нужно отметить, что методы doinsert ( ) и update ( ) на самом деле не являются строго типизированными. Они примут любой подкласс DornainObject без всяких возражений. Вы должны выполнить проверку instanceof и выдать исключение Exception, если будет передан не тот объект. Это позволит уберечься от неизбежных ошибок.

И снова посмотрим, как выглядят, с точки зрения клиента, операции вставки и обновления.

$venue = new woo_domain_Venue();

$venue->setName( «The Likey Lounge» );

// Добавим объект в базу данных

$mapper->insert( $venue );

// Снова найдем объект – просто для проверки, что все работает!

$venue = Çmapper->find( $venue->getld() );

print_r( {venue );

// Изменим объект

$venue->setName( «The Bibble Beer Likey Lounge» );

// Вызовем операцию обновления измененных данных

$mapper->update( $venue );

//И снова обратимся в базе данных, чтобы проверить, что все работает

$venue = $mapper->find( $venue->getld() );

print_r( Svenue );

Комментарии запрещены.