使用JMS序列化器时禁用Doctrine 2延迟加载?

问题描述:

在我的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

在教条中寻找答案后,我的团队发现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

使用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

这很可能被称为丑陋的拐杖,但你可以只选择(),您真正需要的数据,那么结果滋润使用查询对象的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

在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

情况下,你要务实地使用或默认用户,

@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');