Disable Doctrine 2 lazy loading when using JMS Serializer?

前端 未结 5 1290
梦谈多话
梦谈多话 2020-12-05 11:24

Im using Doctrine 2 ORM in my Zend project and need to serialize my Entities to JSON in several cases.

ATM i use the Querybuilder and join all tables i need. But my

相关标签:
5条回答
  • 2020-12-05 11:57

    This may very well be called an ugly crutch, but you could just select() the data that you really need, then hydrate the result to an array using the getArrayResult() method of the Query object...

    0 讨论(0)
  • 2020-12-05 12:03

    When using Doctrine's query builder, you can't disable lazy loading of linked model classes. If you want to bypass such behavior, you better have to request data with Doctrine's DBAL.

    Don't use \Doctrine\ORM\QueryBuilder but \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 讨论(0)
  • 2020-12-05 12:07

    After having looked for the answer in Doctrine, my team figured out that the JMS Serializer was the "problem". It triggered the use of Doctrine Proxies automatically. We wrote a Patch for JMS Serializer to avoid the Lazy Loading.

    We implemented our own DoctrineProxyHandler which just doesn't trigger Doctrines lazyloading mechanism and registered it within our SerializationHandlers Array.

    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;
    }
    

    Now i can simply select my table, join the associations i need - and my JSON will contain just the data i selected instead of infinite depth associations and recursions :)

    $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 will just contain

    {  
      Personappointment: { table {fields}, company {fields}, appointment {fields}}
      Personappointment: { table {fields}, company {fields}, appointment {fields}}
      Personappointment: { table {fields}, company {fields}, appointment {fields}}
      .
      .
    }
    
    0 讨论(0)
  • 2020-12-05 12:09

    Case you want pragmatically use your or default subscriber,

    @DavidLin answer:

    you can copy the class from JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber to Your\Bundle\Event\DoctrineProxySubscriber and comment out the $object->__load(); line

    <?php
    
    /*
     * Copyright 2013 Johannes M. Schmitt <schmittjoh@gmail.com>
     *
     * 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'),
            );
        }
    }
    

    And initialize the serialize like this:

    $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');
    
    0 讨论(0)
  • 2020-12-05 12:11

    In the latest version of JMSSerializer, the place you should look at is

    JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber
    

    instead of

    Serializer\Handler\DoctrineProxyHandler
    

    To override the default lazy load behavior, one should define his own event subscriber.

    In your app/config.yml add this:

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

    you can copy the class from JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber to Your\Bundle\Event\DoctrineProxySubscriber and comment out the $object->__load(); line

    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));
        }
    }
    
    0 讨论(0)
提交回复
热议问题