Игнорировать отсутствующие сущности в доктрине

Мы переносим приложение на Symfony2 и в настоящее время застряли на использовании доктрины ORM. У нас есть куча неверных внешних ключей в базе данных, и становится все труднее выполнять сопоставления отношений, не сталкиваясь с исключением «Сущность не найдена». Планируется очистка базы данных, но, к сожалению, мы не можем заняться этим прямо сейчас. Есть ли способ заставить его просто возвращать значение null, если ему не удается найти нужный объект?

Если у меня есть следующее сопоставление отношений:

    User:
      type: entity
      table: user
      id:
        userID:
          type: integer
          generator:
            strategy: NONE
      fields:
        contactName:
          type: string
          length: 255
          nullable: false
        contactPhone:
          type: string
          length: 255
          nullable: false
        companyName:
          type: string
          length: 255
          nullable: false
        username:
          type: string
          length: 255
          nullable: false
        password:
          type: string
          length: 255
          nullable: false
        email:
          type: string
          length: 255
          nullable: false
      manyToOne:
          address:
            targetEntity: Address
            joinColumn:
              name: addressID
              referencedColumnName: addressID
              nullable: true
              default: null



    -----------------------------------------------------

    Address:
      type: entity
      table: address
      id:
        addressID:
          type: integer
          generator:
            strategy: AUTO
      fields:
        street:
          type: string
          length: 255
          nullable: false
        street2:
          type: string
          length: 255
          nullable: false
        city:
          type: string
          length: 255
          nullable: false
        state:
          type: string
          length: 32
          nullable: false
        zip:
          type: string
          length: 10
          nullable: false
        country:
          type: string
          length: 40
          nullable: false

Кажется, что если в пользовательской таблице есть неправильное значение для addressID, я получу «Объект не найден». исключение при отправке через сериализатор.


person Matt    schedule 21.09.2015    source источник


Ответы (3)


Да, это вполне возможно, но мне сложно сказать, как именно это сделать, потому что вы не привели ни одного примера. Можете ли вы привести несколько существующих примеров кода, чтобы я мог дать вам совет по ним?

Кстати, вы делаете запросы в репозиториях следующим образом:

$qb = $this->entityManager->createQueryBuilder();
$qb->select('e')
    ->from('MyBundle:Entity', 'e')
    ->where($qb->expr()->eq('e.id', ':id'))
    ->setParameter('id', 1)
    ->setMaxResults(1);

$result = $qb->getQuery()->getOneOrNullResult();

Тогда результат будет содержать null, если сущность не найдена. Но я думаю, что в основном методы find* будут возвращать null. Проверьте класс Repository из Doctrine2. Если вам нужно, вы также можете расширить репозиторий Doctrine и переопределить методы на случай, если что-то вызовет исключение. Вы можете поймать его и вернуть null, пустые массивы или любые объекты, созданные вручную... и т.д.

Если вы выполняете какие-либо соединения, также подумайте об использовании левого соединения, если вы не уверены, что данные будут там. Также при доступе к ссылочным объектам в вашем коде, например, если у вас есть:

$user->getProfile();

И есть опасность, что он может вернуть null, проверьте сам метод перед возвратом объекта, примерно так:

public function getProfile()
{
    if ($this->profile === null) {
        return new DummyProfile();
    }

    return $this->profile;
}

Где DummyProfile может расширить ваш исходный объект Profile, и он может быть похож на фиктивный объект, возвращая некоторые значения по умолчанию. Вы также можете сохранить этот фиктивный объект в свойстве и вернуть его в следующий раз, чтобы он не создавался постоянно (одноэлементный шаблон). Таким образом вы можете защитить свой код, чтобы в конечном итоге вы не вызывали методы с нулевым значением, что привело бы к ошибке php.

Надеюсь, это дало вам представление о том, как продолжить... :)

person tomazahlin    schedule 21.09.2015
comment
Спасибо за быстрый ответ! к сожалению, ваше предложение проверить значение null перед возвратом значения свойства не сработало. Я все еще получаю, что сущность не была найдена. исключение. - person Matt; 22.09.2015
comment
Извините, это плохое предложение - причина того, что объект не найден, заключается в том, что Doctrine / Symfony загружает объект из модели, проверка на нуль сначала не остановит это, тем более что ключ не null, из-за чего Symfony/Doctrine пытается загрузить его!! - person Steve Childs; 28.07.2016

Ах, это PITA, которую мы тоже часто видим. Если у вас отключена ленивая загрузка, то Doctrine может в это время загружать и увлажнять все связанные сущности. Но я предполагаю, что он включается и умирает, когда вы пытаетесь отобразить шаблон/форму в Symfony.

С включенной отложенной загрузкой вы не получите ошибку при загрузке объекта, однако вы получите ошибку, если затем попытаетесь получить доступ к свойству связанного объекта (что приведет к его загрузке Doctrine) или визуализировать объект в форма symfony и поле в форме относится к свойству внешней таблицы.

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

person Steve Childs    schedule 28.07.2016

Я только что столкнулся с этой же проблемой и нашел этот вопрос при поиске решения... которого я не нашел. Вот что я сделал (используя аннотацию):

  1. Сделайте отношения ленивыми:

    /**
     * @var Resource
     *
     * @ORM\JoinColumn(name="ResourceID", referencedColumnName="ResourceID")
     * @ORM\OneToOne(targetEntity="Resource", fetch="LAZY")
     */
     private $resource;
     private $_resource_is_valid = null;
    
  2. Измените геттер и всегда используйте его, даже внутри класса:

     /**
      * Returns the associated resource, accounting for bad references.
      * @return Resource|null
      */
     public function getResource()
     {
         if (is_null($this->_resource_is_valid))
         {
             try
             {
                 // We need to call a method that triggers a lazy load
                 if ($this->resource && $this->resource->getID())
                 {
                     $this->_resource_is_valid = true;
                 }
             }
             catch (\Doctrine\ORM\EntityNotFoundException $enf)
             {
                 $this->_resource_is_valid = false;
                 // consider creating a new resource object.
             }
         }
         if ($this->_resource_is_valid)
         {
             return $this->resource;
         }
         return null;
     }
    

Такой подход позволяет Doctrine частично гидратировать сущности и позволяет вам заманить в ловушку плохие отношения в той области, где у вас больше контроля.

person J.D. Pace    schedule 18.07.2018