Сопоставление столбцов базы данных с моделью домена с помощью Zend Framework 2
Я провел немало исследований и не могу найти ответ на этот, казалось бы, популярный вопрос.
У меня есть модель предметной области, которая содержит некоторые свойства, скажем, firstName
а также lastName
-но в своей базе я храню их как fname
а также lname
,
Согласно правилам хорошего PoEAA, модель не должна знать, как она хранится, если вообще существует. Итак, мой вопрос, где идет сопоставление этих полей?
Просматривая источник существующих классов, которые реализуют Zend\Stdlib\Hydrator\AbstractHyrdrator
Интерфейс Я не вижу ни одного из них, которые предлагают функциональность сопоставления.
Кто-нибудь видел чистое решение для этого, которое не загрязняет модель?
ОБНОВЛЕНИЕ: лучшее простое решение
Прошло некоторое время с тех пор, как я задал этот вопрос, но здесь было бы очень простое решение для сопоставления столбцов с объектом без особых накладных расходов. Используя возможности встроенной стратегии именования ZF2, можно добиться желаемого эффекта.
<?php
use Zend\Stdlib\Hydrator\NamingStrategy\UnderscoreNamingStrategy;
use Zend\Stdlib\Hydrator\ObjectProperty;
class Person {
public $fname;
public $lastName;
}
class MyNamingStrategy extends UnderscoreNamingStrategy {
protected $map = [
'fname' => 'first_name'
];
public function hydrate($name) {
$map = array_flip($this->map);
if(isset($map[$name])) {
return $map[$name];
}
return lcfirst(parent::hydrate($name));
}
public function extract($name) {
$map = $this->map;
if(isset($map[$name])) {
return $map[$name];
}
return parent::extract($name);
}
}
$p = new Person();
$h = new ObjectProperty();
$h->setNamingStrategy(new MyNamingStrategy());
$h->hydrate([
'first_name' => 'john',
'last_name' => 'Doe'
],$p);
var_dump($p);
var_dump($h->extract($p));
Выходы:
object(Person)[4]
public 'fname' => string 'john' (length=4)
public 'lastName' => string 'Doe' (length=3)
array (size=2)
'first_name' => string 'john' (length=4)
'last_name' => string 'Doe' (length=3)
2 ответа
Использование ORM
Я бы использовал любой ORM, который позволяет вам настраивать сопоставления вне классов моделей.
Лично мне нравится учение. Я использую для сопоставления полей с базой данных с помощью аннотаций docbloc, поскольку это проще, но я согласен, что информация о том, как хранятся данные, не должно быть внутри модели. К счастью, у вас есть опции XML и YAML для отображения данных.
Например, если у вас есть эта сущность в вашей модели:
<?php
class Message
{
private $id;
private $text;
private $postedAt;
}
Вы можете настроить это отображение в XML
<doctrine-mapping>
<entity name="Message" table="message">
<field name="id" type="integer" />
<field name="text" length="140" />
<field name="postedAt" column="posted_at" type="datetime" />
</entity>
</doctrine-mapping>
Таким образом, сопоставления находятся в файлах конфигурации, поэтому сама модель не знает, как она сохраняется.
У вас есть модуль DoctrineORMModule для простой интеграции Doctrine в ZF2:
Если вы не хотите использовать ORM
Вы можете создать сопоставления в конфигурационных файлах. После этого вы используете шаблон табличного шлюза, после чего вы можете внедрить этот конфиг в класс Table. Я приведу вам пример адаптации модуля "Альбом" из приложения скелета ZF2 (вы можете адаптировать его к реальному сценарию, я думаю, что это проще, чем если бы мне пришлось изучать вашу систему)
Вы создаете сопоставления в файле module.config.php. Вы сопоставляете имя класса с таблицей, а каждое поле в классе - с именем столбца.
'mappings' => array ( 'AlbumTable' => array ( 'table'=> 'TABLENAME', 'fields' => array ( 'field1_en_clase' => 'field1_en_db', 'field2_en_clase' => 'field2_en_db', ) ), 'OTHER_ENTITY' => array ( 'table'=> 'TABLENAME', 'fields' => array ( 'field1_en_clase' => 'field1_en_db', 'field2_en_clase' => 'field2_en_db', ) ), //... );
Затем, когда вы настраиваете службу в Module.php, вы отправляете эту информацию классам, которые нуждаются в них:
class Module { public function getServiceConfig() { return array( 'factories' => array( 'Album\Model\AlbumTable' => function($sm) { $config = $sm->get('AlbumTableGateway'); $tableGateway = $sm->get('AlbumTableGateway'); //when you create the AlbumTable class, you send the field mappings $table = new AlbumTable($tableGateway, $config ['mappings']['AlbumTable']['fields']); return $table; }, 'AlbumTableGateway' => function ($sm) { $config = $sm->get('AlbumTableGateway'); $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter'); $resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype(new Album()); //when you create the TableGateway, you also take the table name from the config. return new TableGateway($config ['mappings']['AlbumTable']['table'] , $dbAdapter, null, $resultSetPrototype); }, ), ); } }
Затем вам просто нужно адаптировать свой класс AlbumTable, чтобы он получал и использовал эти сопоставления.
class AlbumTable { protected $tableGateway, $mappings; //First, in the constructor, you receive and save the mappings public function __construct(TableGateway $tableGateway, $mappings) { $this->tableGateway = $tableGateway; $this->mappings = $mappings; } //then in every function where you before hardcoded the column names, now you take it from the mappings, for instance: public function saveAlbum(Album $album) { /* here you had $data = array( 'artist' => $album->artist, 'title' => $album->title, ); */ // now you change it to: $data = array( $mappings['artist'] => $album->artist, $mappings['title'] => $album->title, ); $id = (int) $album->id; if ($id == 0) { $this->tableGateway->insert($data); } else { if ($this->getAlbum($id)) { $this->tableGateway->update($data, array('id' => $id)); } else { throw new \Exception('Album id does not exist'); } } }
И везде одинаково имя столбца.
Я думаю, что вы можете легко адаптировать это к вашей системе.
Вы смотрели на шаблон проектирования Data Mapper? Я реализовал это раньше в PHP (несколько приложений ZF1), и я думаю, что он делает то, что вы ищете. Вы возлагаете ответственность за знание базы данных на уровень картографа, при этом объекты модели (сущности) ничего не знают об этом. Это довольно просто реализовать, и вам не нужно использовать более сложный ORM, такой как Doctrine.
Редактировать: вот описание картины Фаулера
И вот сообщение в блоге с небольшим примером того, как вы могли бы реализовать это в PHP