Doctrine 2: аксессоры, мутаторы, fromArray(), toArray()

// Февраль 20th, 2012 // Doctrine 2

Всем хороша новая Doctrine, вот только нет там многих удобных функций, решающих повседневные проблемы. В этой статье я расскажу о том, как можно сделать Doctrine 2 более комфортной для разработки.

Основное нововведение Doctrine 2 в том, что классы моделей (entities) теперь ни от кого не наследуются. А значит нет функций, добавляемых раньше через Doctrine_Record. Особенно сбивает с толку отсутствие аксессоров и мутаторов для свойств модели. Я поначалу создавал их вручную, но потом понял, что лучше сделать один раз и качественно доступ к свойствам модели. Итак вот пошаговое руководство. Проверено на Doctrine 2.2

  1. Создаём класс DoctrineExtra\ORM\DomainObject.php (содержание файла ниже).
  2. Наследуем все классы модели от него.
  3. Обозначаем все свойства модели как public. Возможно лучше будет сделать их protected.
  4. Теперь вам становятся доступны аксессоры, мутаторы, функции 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

Share


Смотрите также:

6 Responses to “Doctrine 2: аксессоры, мутаторы, fromArray(), toArray()”

  1. Описка у тебя в конце:
    > в реестр (Zend_Gegistry)

  2. Andrew:

    На мой взгляд, выглядит нескоклько хардкорно.

Комментировать