Doctrine 2: аксессоры, мутаторы, fromArray(), toArray()
// Февраль 20th, 2012 // Doctrine 2
Всем хороша новая Doctrine, вот только нет там многих удобных функций, решающих повседневные проблемы. В этой статье я расскажу о том, как можно сделать Doctrine 2 более комфортной для разработки.
Основное нововведение Doctrine 2 в том, что классы моделей (entities) теперь ни от кого не наследуются. А значит нет функций, добавляемых раньше через Doctrine_Record. Особенно сбивает с толку отсутствие аксессоров и мутаторов для свойств модели. Я поначалу создавал их вручную, но потом понял, что лучше сделать один раз и качественно доступ к свойствам модели. Итак вот пошаговое руководство. Проверено на Doctrine 2.2
- Создаём класс DoctrineExtra\ORM\DomainObject.php (содержание файла ниже).
- Наследуем все классы модели от него.
- Обозначаем все свойства модели как public. Возможно лучше будет сделать их protected.
- Теперь вам становятся доступны аксессоры, мутаторы, функции fromArray() и toArray(). Причём toArray() работает рекурсивно по связям модели.
DoctrineExtra\ORM\DomainObject.php
<?php
namespace DoctrineExtra\ORM;
abstract class DomainObject implements \ArrayAccess
{
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
public function __get($offset) {
return $this->offsetGet($offset);
}
public function __set($offset, $value = NULL) {
if($this->offsetExists($offset)) {
if($value == NULL) {
$this->offsetUnset($offset);
} else {
$this->offsetSet($offset, $value);
}
}
}
public function fromArray($array = array())
{
$entity = $this;
\array_walk($array, function(&$value, &$key) use(&$entity) {
$entity->offsetSet ($key, $value);
});
return $this;
}
/**
* Работает и для составных ключей тоже
*/
public function getPK()
{
$em = \Zend_Registry::get('entityManager');
$meta = $em->getClassMetadata(get_class($this));
$pk = $meta->getIdentifierFieldNames();
return $pk;
}
/**
* Возвращает только непосредственный свойства модели
* (без обхода связей)
*/
public function toArrayDirectly()
{
return get_object_vars($this);
}
/**
* Рекурсивно по модели и её связям
*/
public function toArray() {
$em = \Zend_Registry::get('entityManager');
$pks = $this->getPK();
$entity = $this;
$values = array();
$entityState = $em->getUnitOfWork()->getEntityState($this);
// Для записей, не сохранённых в БД, возвращаем просто массив значений свойств
if($entityState == \Doctrine\ORM\UnitOfWork::STATE_NEW) {
return $this->toArrayDirectly();
}
array_walk($pks, function ($pk) use (&$entity, &$values) {
$values[$pk] = $entity[$pk];
});
if($pks == NULL)
throw new \Exception('Can not doing toArray() to model without loaded PK (id)');
$tmpMergedMappings = array();
$tmpFieldMappings = array();
$tmpAssocMappings = array();
$testObj = $em->getRepository(get_class($this))->findOneBy($values);
$testJob = $testObj->job;
$tmpFieldMappings = $em->getClassMetadata(get_class($this))->fieldMappings;
$tmpAssocMappings = array_keys($em->getClassMetadata(get_class($this))->associationMappings);
foreach($tmpFieldMappings as $fmKey => $fmValue) {
if(is_object($this->$fmKey)) {
if (get_class($this->$fmKey) == "DateTime" ) {
switch ($tmpFieldMappings[$fmKey]["type"]) {
case "sndatetype":
$tmpMergedMappings[$fmKey] = $this->$fmKey->format('m/d/Y');
break;
// handle any custom types..
default:
$tmpMergedMappings[$fmKey] = $this->$fmKey->format('Y-m-d H:i:s');
break;
}
} else {
// presume the default _id mapping...
$key_id = $fmKey."_id";
$tmpMergedMappings[$key_id] = $this->$key_id->id;
}
} else {
$tmpMergedMappings[$fmKey] = $this->$fmKey;
}
}
foreach($tmpAssocMappings as $amKey => $amValue) {
$tmpKey = $amValue."_id";
switch (get_class($this->$amValue)) {
case "Doctrine\ORM\PersistentCollection":
// dont do anything with these right now..
break;
default:
// Trigger the loading via the proxy.
if(method_exists($this->$amValue, 'forceEagerLoad')) {
$forced = $this->$amValue->forceEagerLoad();
} else {
// Note: these classes dont have/inherit a forceEagerLoad() method,
// or we are trying to call it on something not set yet.
//var_dump(get_class($this->$amValue));
//var_dump($amValue);
}
if($this->$amValue) {
if($this->$amValue->id != null) {
$tmpMergedMappings[$tmpKey] = $this->$amValue->id;
}
}
break;
}
}
return $tmpMergedMappings;
}
}
Обратите внимание, что в процессе загрузки (bootstrap) в реестр (Zend_Registry) должен добавляться entityManager.
Удачного вам кодинга!
Ссылки
Implementing ArrayAccess for Domain Objects








Описка у тебя в конце:
> в реестр (Zend_Gegistry)
О, спасибо, Олег!
Исправил.
На мой взгляд, выглядит нескоклько хардкорно.
С удовольствием узнаю о других способах. Какие есть идеи?
Мне больше нравятся мапперы
Так это же и есть DataMapper. См. http://marco-pivetta.com/doctrine2-orm-tutorial/