Игнорирайте липсващите обекти в доктрината

Ние пренасяме приложение към Symfony2 и в момента сме блокирани, използвайки doctrine ORM. Имаме куп лоши външни ключове в базата данни и става все по-трудно да правим съпоставяния на релациите, без да попаднем в изключение „Обектът не е намерен“. В пътната карта е да изчистим базата данни, но не е нещо, с което можем да се справим в момента, за съжаление. Има ли някакъв начин да го накарам просто да върне нула, ако не успее да потърси правилния обект?

Ако имам следното картографиране на връзката:

    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* ще върнат нула. Проверете класа Repository от Doctrine2. Ако имате нужда, можете също да разширите хранилището на Doctrine и да замените методите, в случай че нещо хвърли изключение. Можете да го хванете и да върнете нула, празни масиви или всякакви ръчно изработени обекти... и т.н.

Ако правите някакви съединявания, помислете и за използването на ляво съединяване, ако не сте сигурни, че данните ще бъдат там. Също така при достъп до реферирани обекти във вашия код, например ако имате:

$user->getProfile();

И има опасност да върне null, проверете в самия метод, преди да върнете обекта, нещо подобно:

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

    return $this->profile;
}

Където DummyProfile може да разшири вашия оригинален обект Profile и може да бъде като макет обект, връщащ някои стойности по подразбиране. Можете също така да запишете този фиктивен обект в свойство и да го върнете следващия път, така че да не се инстанцира през цялото време (модел с единичен обект). Това е начинът, по който можете да защитите кода си, така че да не се окаже, че извиквате методи на null, което би довело до php грешка.

Надявам се това да ви даде идея как да продължите... :)

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

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

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

За съжаление няма лесен начин да спрете или хванете това. Моят съвет би бил ръчно да проверите данните за лоши външни ключове и да почистите данните, мързеливото зареждане на Doctrine е особено непримиримо към лошите данни и Symfony просто влошава това, като издухва с грешка 500, ако попадне на duff връзка.

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