2012-07-20 48 views
15

在我的Zend項目中使用Doctrine 2 ORM並需要在幾種情況下將我的實體序列化爲JSON。使用JMS序列化器時禁用Doctrine 2延遲加載?

ATM我使用Querybuilder並加入我需要的所有表。但是我的序列化器會導致原則惰性加載每個關聯的實體,這會導致相當大的數據量並引發遞歸。

現在我正在尋找一種方法來完全禁用Doctrines惰性加載行爲。

我的方式來選擇數據將是如下:

$qb= $this->_em->createQueryBuilder() 
      ->from("\Project\Entity\Personappointment", 'pa') 
      ->select('pa', 't', 'c', 'a', 'aps', 'apt', 'p') 
      ->leftjoin('pa.table', 't') 
      ->leftjoin('pa.company', 'c') 
      ->leftjoin('pa.appointment', 'a') 
      ->leftjoin('a.appointmentstatus', 'aps') 
      ->leftjoin('a.appointmenttype', 'apt') 
      ->leftjoin('a.person','p') 

我想我的結果集只包含選定表和協會。

任何幫助將不勝感激。

+0

你可以發佈你的實體? – Stony 2012-07-23 09:11:28

+0

如果您使用JMS序列化程序,請依靠我的答案。 如果您想完全避免使用JMS Serializer,請依靠Exanders Answer(特別是註釋)。 – 2012-07-31 13:47:35

回答

9

在教條中尋找答案後,我的團隊發現JMS Serializer是「問題」。 它自動觸發了Doctrine Proxies的使用。我們爲JMS序列化程序編寫了一個補丁以避免延遲加載。

我們實施了我們自己的DoctrineProxyHandler,它不會觸發Doctrines lazyloading機制並將其註冊到我們的SerializationHandlers數組中。

class DoctrineProxyHandler implements SerializationHandlerInterface { 

public function serialize(VisitorInterface $visitor, $data, $type, &$handled) 
{ 
    if (($data instanceof Proxy || $data instanceof ORMProxy) && (!$data->__isInitialized__ || get_class($data) === $type)) { 
     $handled = true; 

     if (!$data->__isInitialized__) { 

      //don't trigger doctrine lazy loading 
      //$data->__load(); 

      return null; 
     } 

     $navigator = $visitor->getNavigator(); 
     $navigator->detachObject($data); 

     // pass the parent class not to load the metadata for the proxy class 
     return $navigator->accept($data, get_parent_class($data), $visitor); 
    } 

    return null; 
} 

現在我可以簡單地選擇我的表,加入我需要的協會 - 和我的JSON將僅包含我選擇的,而不是無限的深度關聯和遞歸:)數據

$qb= $this->_em->createQueryBuilder() 
     ->from("\Project\Entity\Personappointment", 'pa') 
     ->select('pa', 't', 'c', 'a') 
     ->leftjoin('pa.table', 't') 
     ->leftjoin('pa.company', 'c') 
     ->leftjoin('pa.appointment', 'a') 

JSON只會包含

{ 
    Personappointment: { table {fields}, company {fields}, appointment {fields}} 
    Personappointment: { table {fields}, company {fields}, appointment {fields}} 
    Personappointment: { table {fields}, company {fields}, appointment {fields}} 
    . 
    . 
} 
+0

如何啓用自定義代理處理程序? – vinnylinux 2012-08-08 23:05:22

+0

要小心,我們發現有幾個地方會觸發延遲加載,儘管這些地方的使用較少。如果被忽略,會導致奇怪的行爲。 – 2012-11-02 07:02:49

+2

特別是當加載數據集合(Doctrine \ ORM \ PersistentCollection)時。 – 2012-11-17 19:05:32

3

使用Doctrine的查詢生成器時,不能禁用鏈接模型類的延遲加載。如果你想繞過這種行爲,你最好不得不用Doctrine的DBAL來請求數據。

請勿使用\Doctrine\ORM\QueryBuilder,而應使用\Doctrine\DBAL\Query\QueryBuilder

$qb = new QueryBuilder($this->_em->getConnection()); 
$expr = $qb->expr(); 

$qb->select('pa.*', 't.*', 'c.*', 'a.*', 'aps.*', 'apt.*', 'p.*') 
    ->from('person_appointment', 'pa') 
    ->leftJoin('pa', 'table', 't', $expr->eq('pa.table_id', 't.table_id')) 
    // put other joints here 
    // ... 
    ->leftjoin('a', 'person', 'p', $expr->eq('a.person_id', 'p.person_id')); 
+0

嗨!謝謝,這首先看起來不錯 - 但似乎DBAL qb只是能夠構建SQL語句,我可以在之後使用PDO執行。所有ORM功能缺失。 – 2012-07-25 12:22:22

+0

問題是你想要忽略ORM功能!您無法加載具有Doctrine ORM的模型類而無需加載相關的模型類。 – Florent 2012-07-25 13:57:09

+0

也許我會從錯誤的方面接近問題,並應該開始修改序列化程序。但Serializer無法知道它所調用的關聯是否已經存在,或者在調用時是否延遲加載。 我的願望是在JOIN中沒有提到的關聯只是NULL而不是懶加載的。 – 2012-07-26 06:58:16

4

這很可能被稱爲醜陋的柺杖,但你可以只選擇(),您真正需要的數據,那麼結果滋潤使用查詢對象的getArrayResult()方法的數組..

+0

我們首先嚐試了這種方法。但是我們有一個相當大的數據庫模型並使用了Doctrines Mapped Superclass。所以這個簡單的方法只要我們不選擇多個具有相同命名字段的實體就行。 例如:我們正在使用Doctrines「Version」字段,它幾乎存在於每個實體中。我將不得不手動使用「select as」entityname_version這是 - 如你所說 - 一個醜陋的柺杖:( – 2012-07-25 12:28:06

+1

我不知道,但不會實際返回一個數組數組,其中每個二級數組代表一個水合對象?..這意味着如果通過表名選擇,將不會有任何字段名稱衝突。* – Exander 2012-07-25 14:06:29

+0

關聯實體的數據是否存儲在單獨的數組中,而該數組又會存儲在適當的關鍵字下在數組中表示對象關聯(雖然我不太清楚雙向關聯)。 – Exander 2012-07-25 14:15:24

11

在JMSSerializer的最新版本,這個地方你應該看就是

JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber 

代替

Serializer\Handler\DoctrineProxyHandler 

要覆蓋默認延遲加載行爲,應該定義自己的事件訂閱。

在你app/config.yml補充一點:

parameters: 
    ... 
    jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber 

您可以複製從JMS \串行\此事件\用戶\ DoctrineProxySubscriber類到您的\包\事件\ DoctrineProxySubscriber並註釋掉$對象 - > __負載( );線

public function onPreSerialize(PreSerializeEvent $event) 
{ 
    $object = $event->getObject(); 
    $type = $event->getType(); 

    // If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not 
    // modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created, 
    // so it must be loaded if its a real class. 
    $virtualType = ! class_exists($type['name'], false); 

    if ($object instanceof PersistentCollection) { 
     if (! $virtualType) { 
      $event->setType('ArrayCollection'); 
     } 

     return; 
    } 

    if (! $object instanceof Proxy && ! $object instanceof ORMProxy) { 
     return; 
    } 

    //$object->__load(); Just comment this out 

    if (! $virtualType) { 
     $event->setType(get_parent_class($object)); 
    } 
} 
+2

謝謝。這很有幫助。但PersistentCollection對象仍在加載中。任何解決方案來禁用此? – nkobber 2015-09-16 14:33:44

+0

@nkobber,你好我也面臨同樣的問題,你找到解決辦法嗎? – vishal 2016-05-09 07:18:37

1

情況下,你要務實地使用或默認用戶,

@DavidLin答案:

你可以從JMS \串行\此事件\用戶\ DoctrineProxySubscriber類複製到您的\包\ Event \ DoctrineProxySubscriber並註釋掉$ object - > __ load();線

<?php 

/* 
* Copyright 2013 Johannes M. Schmitt <[email protected]> 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
*  http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

namespace Your\Bundle\Event; 

use Doctrine\ORM\PersistentCollection; 
use Doctrine\ODM\MongoDB\PersistentCollection as MongoDBPersistentCollection; 
use Doctrine\ODM\PHPCR\PersistentCollection as PHPCRPersistentCollection; 
use Doctrine\Common\Persistence\Proxy; 
use Doctrine\ORM\Proxy\Proxy as ORMProxy; 
use JMS\Serializer\EventDispatcher\PreSerializeEvent; 
use JMS\Serializer\EventDispatcher\EventSubscriberInterface; 

class AvoidDoctrineProxySubscriber implements EventSubscriberInterface 
{ 
    public function onPreSerialize(PreSerializeEvent $event) 
    { 
     $object = $event->getObject(); 
     $type = $event->getType(); 

     // If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not 
     // modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created, 
     // so it must be loaded if its a real class. 
     $virtualType = ! class_exists($type['name'], false); 

     if ($object instanceof PersistentCollection 
      || $object instanceof MongoDBPersistentCollection 
      || $object instanceof PHPCRPersistentCollection 
     ) { 
      if (! $virtualType) { 
       $event->setType('ArrayCollection'); 
      } 

      return; 
     } 

     if (! $object instanceof Proxy && ! $object instanceof ORMProxy) { 
      return; 
     } 


     //Avoiding doctrine lazy load proxyes 
     //$object->__load(); 

     if (! $virtualType) { 
      $event->setType(get_parent_class($object)); 
     } 
    } 

    public static function getSubscribedEvents() 
    { 
     return array(
      array('event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize'), 
     ); 
    } 
} 

和初始化序列化這樣

$serializer = JMS\Serializer\SerializerBuilder::create() 
    //remove this to use lazy loading 
    ->configureListeners(function(JMS\Serializer\EventDispatcher\EventDispatcher $dispatcher) { 
     $dispatcher->addSubscriber(new Your\Bundle\Event\AvoidDoctrineProxySubscriber()); 
    }) 
    // !remove this to use lazy loading 
    ->build(); 

//and serialize the data with/without Lazy 

serializer->serialize($data, 'json');