Doctrine 2: аксессоры, мутаторы, fromArray(), toArray()
// 20 февраля, 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/
Fatal error: Cannot use object of type Entity\Student as array in /home/horoshev/www-data/kurs/www/app/Entity/Base.php on line 133
Разобрался. Все таки забыл интерфейс заимплементить.
Отлично!
Не работает рекурсивная сериализация
просто нет никаких вложенных элементов
смущает вот этот кусок кода
$key_id = $fmKey.»_id»;
108.$tmpMergedMappings[$key_id] = $this->$key_id->id;
у меня внешние ключи имеют другой формат названия (совпадающий с названием праймари кея в присоединяемой таблице)
Хм, у меня вроде работала. На досуге надо будет ещё разок протестить.
А вот это вообще странный код.
Это вообще проверялось?
public function toArrayDirectly()
{
return get_object_vars($this);
}
данная штука просто вывалит ВСЕ свойства объекта, включая совсем неимеющие отношения к данным свойства. Добавленные уже самой доктриной.
Да, она делает именно это. Потом либо разбирать их по маппингу модели, либо по другому получать 🙂
«Теперь вам становятся доступны аксессоры, мутаторы, функции fromArray() и toArray(). Причём toArray() работает рекурсивно по связям модели.»
Вот еще интересно на какую глубину. Ведь приналичии двухсторнних связей вы легко получите бесконечную рекурсию.
Статья содержит связь с Коментариями.
Каждый Комментарий имеет связь со Статьей.
Предположим мы сериализуем в массив статью рекурсивно, также сериализуем всее ее комментарии, котрые в свою очередь содержат статью, содержащую этиже комментарии …. и так бесконечно.
Я вижу выход только в том чтобы явно указывать какие связи для данной сущьности мы будем раскрывать.
Да, обработки циклических ссылок нет. Хорошая идея указывать раскрываемые сущности или глубину связей.