Disable Doctrine 2 Lazy Loading When Using Jms Serializer

Disable Doctrine 2 lazy loading when using JMS Serializer?

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

How to prevent lazy loading of vulnarable entities in Symfony

I would suggest to use JMSSerializerBundle for that. It is a widely used bundle, also in huge API's. You can exactly configure which properties should be exposed and which not. You can also build groups for exposing properties and use a specific exclusion strategy. Check the documentation for further information.

Hint: also check the Limiting serialization depth for deep nested objects.

Avoiding recursion with Doctrine entities and JMSserializer

Check the Serializer/Handler/DoctrineProxyHandler.php file on JMSSerializerBundle. Now, if you comment this line:

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__) {
//$data->__load();
}

It will stop lazy loading your entities. If this is what you're looking for, then just go ahead and create your own handler where you don't lazy load.

If this isn't correct, I recommend that you customize your entities before sending them to JMSSerializerBundle at your taste. For example, in any related entities I want the ID, while in others i need a custom column name like code, or name, or anything.

I just create a copy of my entity object and then start getting the fields I need for relationships. Then, I serialize that copy. JMSSerializerBundle won't lazy load because I already provided the proper fields.

Symfony serializer ignore serialization groups

The Serializer is not able to see whether a call will trigger the lazy loading in Doctrine. You probably could write your own Normalizer that looks for classes from Doctrine's Proxy-namespace, but I imagine that will probably just becoome a hot mess if it will solve your problem in the first place.

The best solution that I can think of is tackling the issue at the ORM-level, as that is what is causing you problems.

For example you could create a custom find-method in a repository using DQL or QueryBuilder, which will fetch all necessary data including potentially joining in related objects you want to output and excluding any fields you don't want. Then I would hydrate the result (see Doctrine Docs: Hydration) using a different Hydrator. You could try the SimpleObjectHydrator provided by Doctrine. As far as I know, this Hydrator will not use Proxies for Lazy Loading and instead will load as much as it can eagerly and it should still give you your original Entity, but the eager loading might not be what you want. In that case you can use it as a reference for your own Hydrator. Writing custom hydrators might be a huge pain and you could instead opt for array output and just forego the object hydration entirely. In that case you are sure to lose the annotations in your entity, which you would then need to address in your Serializer logic.

You could also look at Native Queries (i.e. SQL) and then custom Result Set Mapping as an alternative: see Doctrine Docs: ResultSetMappingBuilder

Advice for implementing field whitelists with Symfony/FosRestBundle/JMS Serializer

You should be able to achieve what you want with the Groups exclusion strategy.

For example, your Post entity could look like this:

use JMS\Serializer\Annotation as JMS;

/**
* @JMS\ExclusionPolicy("all")
*/
class Post
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
* @ORM\Column(type="integer")
*
* @JMS\Expose
* @JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $id;

/**
* @ORM\Column(type="string")
*
* @JMS\Expose
* @JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $title;

/**
* @JMS\Expose
* @JMS\Groups({"all", "withFooAssociation"})
*
* @ORM\OneToMany(targetEntity="Foo", mappedBy="post")
*/
private $foos;
}

Like this, if your controller action returns a View using serializerGroups={"all"}, the Response will contains all fields of your entity.

If it uses serializerGroups={"withFooAssociation"}, the response will contains the foos[] association entries and their exposed fields.

And, if it uses serializerGroups={"withoutAssociation"}, the foos association will be excluded by the serializer, and so it will not be rendered.

To exclude properties from the target entity of the association (Fooentity), use the same Groups on the target entity properties in order to get a chained serialisation strategy.

When your serialization structure is good, you can dynamically set the serializerGroups in your controller, in order to use different groups depending on the include and fields params (i.e. /posts?fields[]=id&fields[]=title). Example:

// PostController::getAction

use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder;

$serializer = SerializerBuilder::create()->build();
$context = SerializationContext::create();
$groups = [];

// Assuming $request contains the "fields" param
$fields = $request->query->get('fields');

// Do this kind of check for all fields in $fields
if (in_array('foos', $fields)) {
$groups[] = 'withFooAssociation';
}

// Tell the serializer to use the groups previously defined
$context->setGroups($groups);

// Serialize the data
$data = $serializer->serialize($posts, 'json', $context);

// Create the view
$view = View::create()->setData($data);

return $this->handleView($view);

I hope that I correctly understood your question and that this will be sufficient for help you.

Doctrine2, PersistentCollection and JMS Serializer

The PersistentCollection object is an Iterator Aggregate and not an array. The distinction is that an Iterator is an object that can be iterated over and so may or may not contain the data required for serializing to an array at any one time.

To serialize the Collection as JSON, try the following:

$serializer = $container->get('serializer');
$arr = $this->getQueuedItems()->toArray();
$json = $serializer->serialize($arr, 'json');

If you're not too fussed about the keys, you could also use getValues, rather than toArray.



Related Topics



Leave a reply



Submit