Избегание рекурсии с объектами Doctrine и JMSserializer
Я создаю REST API, используя Symfony2, Doctrine, FOSRestBundle и JMSSerializer.
Проблема, с которой я столкнулась, - это сериализация моих объектов, сериализатор тянет любые связанные объекты. Например, для задачи, которая является частью истории, которая является частью доски, поэтому при сериализации задачи я получаю вывод, который включает в себя историю, которая включает в себя плату, которая затем включает в себя все другие истории на доске.
Есть ли простой способ ограничить это, и просто включите foreignIds вместо этого?
Ответы
Ответ 1
Проверьте файл Serializer/Handler/DoctrineProxyHandler.php на JMSSerializerBundle. Теперь, если вы прокомментируете эту строку:
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();
}
Это прекратит ленивую загрузку ваших объектов. Если это то, что вы ищете, тогда просто идите, создайте свой собственный обработчик, где вы не будете ленивой загрузкой.
Если это неверно, я рекомендую вам настроить свои объекты перед отправкой их на JMSSerializerBundle по своему вкусу. Например, в любых связанных объектах я хочу ID, в то время как в других мне нужно собственное имя столбца, например код или имя, или что-то еще.
Я просто создаю копию моего объекта entity, а затем начинаю получать поля, необходимые для отношений. Затем я сериализую эту копию. JMSSerializerBundle не будет ленивой загрузкой, потому что я уже предоставил правильные поля.
Ответ 2
Использовать политику исключения JMS.
Пример использования аннотаций в категории, где вы не хотите включать дочерние элементы и связанные с продуктом объекты, которые должны быть включены:
use ...
JMS\SerializerBundle\Annotation\ExclusionPolicy,
JMS\SerializerBundle\Annotation\Exclude,
...;
/**
* ...
* @ExclusionPolicy("none")
*/
class Category
{
/**
* ...
* @Exclude
*/
private $children;
/**
* ...
* @Exclude
*/
private $products;
}
Дополнительную информацию смотрите в JMSSerializer docs.
EDIT:
Например, вы можете использовать частичное ключевое слово, чтобы выбрать только те данные, которые вам нужны. Хотя я не мог, для жизни меня,
отключите загрузку полных связанных объектов (два уровня вниз), если я передаю объект объекта в сериализатор (даже при отключении нагрузки в DoctrineProxyHandler)
но если я использую массив, то он не использует доктрину ленивой загрузки, хотя прокси (как и ожидалось).
Пример использования ваших объектов-примеров:
$dql = "SELECT t, s, partial b.{id}, partial ss.{id}
FROM Acme\AppBundle\Entity\Task t
JOIN t.story s
JOIN s.board b
JOIN b.stories ss"
$q = $this->_em-createQuery($dql);
$result = $q->getArrayResult();
Таким образом вы получите что-то вроде:
[
{
id: 33,
title: "My Task",
story: [
{
id: 554,
board: [
{
id: 14,
stories: [
{
id: 554
},
{
id: 3424
},
{
id: 3487
}
]
}
]
}
]
}
]
P.S. Я действительно заинтригован этой "проблемой". В любом случае я увижу решение о том, как сериализовать объект объекта без использования результата массива.
Ответ 3
Просто обновление в последней версии JMSSerializer, место, которое вы должны посмотреть, это
JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber
вместо
Serializer\Handler\DoctrineProxyHandler
Чтобы переопределить поведение ленивой загрузки по умолчанию, нужно определить его собственный подписчик событий.
В вашем приложении /config.yuml добавьте это:
parameters:
...
jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber
вы можете скопировать класс из JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber в свой \Bundle\Event\DoctrineProxySubscriber и прокомментировать $object → __ load(); Линия
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));
}
}
Обновление. В итоге я написал собственную упрощенную версию инструмента сериализации: https://github.com/dlin-me/array-converter-bundle
Ответ 4
Здесь функция для выбора идентификаторов взаимно-однозначных или связанных друг с другом объектов в общих чертах без использования объединений.
function selectWithAssociations($doctrine, $className) {
$em = $doctrine->getManager();
$meta = $em->getClassMetadata($className);
//explicitly get IDs of associated entities
$assocClauses = array();
foreach ($meta->getAssociationMappings() as $assocName => $assoc) {
if (isset($assoc['joinTable'])) {
//todo: doesn't handle many to many associations
} else {
$assocClauses[] = ", IDENTITY(e.$assocName) AS $assocName";
}
}
//run custom DQL query
$q = $em->createQuery('SELECT e AS _d' . implode('', $assocClauses) . ' FROM ' . $className . ' e');
$result = $q->getArrayResult();
return $result;
}
Ответ 5
Вот класс, который предотвращает ленивую загрузку одной или многих ассоциаций, которые могут использоваться как JMS Serializer ExclusionStrategy.
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy;
use JMS\Serializer\Context;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\SerializationContext;
/**
* Class OnlyLoadedAssociationsExclusionStrategy
*
* http://stackoverflow.com/info/11851197/avoiding-recursion-with-doctrine-entities-and-jmsserializer
*/
class OnlyLoadedAssociationsExclusionStrategy implements ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
}
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
if ($context instanceof SerializationContext){
$vistingSet=$context->getVisitingSet();
//iterate over object to get last object
foreach ($vistingSet as $v){
$currentObject=$v;
}
$propertyValue=$property->getValue($currentObject);
if ($propertyValue instanceof Proxy){
// skip not loaded one association
if (!$propertyValue->__isInitialized__){
return true;
}
}
if ($propertyValue instanceof PersistentCollection){
// skip not loaded many association
if (!$propertyValue->isInitialized()){
return true;
}
}
}
return false;
}
}
Пример использования:
$serializationContext->addExclusionStrategy(
new OnlyLoadedAssociationsExclusionStrategy()
);