Doctrine 2: аксессоры, мутаторы, fromArray(), toArray()
Всем хороша новая Doctrine, вот только нет там многих удобных функций, решающих повседневные проблемы. В этой статье я расскажу о том, как можно сделать Doctrine 2 более комфортной для разработки.
Основное нововведение Doctrine 2 в том, что классы моделей (entities) теперь ни от кого не наследуются. А значит нет функций, добавляемых раньше через Doctrine_Record. Особенно сбивает с толку отсутствие аксессоров и мутаторов для свойств модели. Я поначалу создавал их вручную, но потом понял, что лучше сделать один раз и качественно доступ к свойствам модели. Итак вот пошаговое руководство. Проверено на Doctrine 2.2
- Создаём класс DoctrineExtra\ORM\DomainObject.php (содержание файла ниже).
- Наследуем все классы модели от него.
- Обозначаем все свойства модели как public. Возможно лучше будет сделать их protected.
- Теперь вам становятся доступны аксессоры, мутаторы, функции fromArray() и toArray(). Причём toArray() работает рекурсивно по связям модели.
DoctrineExtra\ORM\DomainObject.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
<?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.
Удачного вам кодинга!
Описка у тебя в конце:
> в реестр (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() работает рекурсивно по связям модели.”
Вот еще интересно на какую глубину. Ведь приналичии двухсторнних связей вы легко получите бесконечную рекурсию.
Статья содержит связь с Коментариями.
Каждый Комментарий имеет связь со Статьей.
Предположим мы сериализуем в массив статью рекурсивно, также сериализуем всее ее комментарии, котрые в свою очередь содержат статью, содержащую этиже комментарии …. и так бесконечно.
Я вижу выход только в том чтобы явно указывать какие связи для данной сущьности мы будем раскрывать.
Да, обработки циклических ссылок нет. Хорошая идея указывать раскрываемые сущности или глубину связей.