将包含递归关系的实体呈现为JSON

将包含递归关系的实体呈现为JSON,json,rest,symfony,doctrine-orm,entity-relationship,Json,Rest,Symfony,Doctrine Orm,Entity Relationship,我正在使用带Doctrine2的Symfony2.4构建一个基于JSON的RESTAPI 编辑:使用JsonNormalizer,我可以禁用一些属性,但是如果我想设置它们,而不需要递归性呢 基本上,我现在(工作)的是: 我想要的是: { "tasks": [ { "id": 1, "name": "first task", "category": { "id": 1,

我正在使用带Doctrine2的Symfony2.4构建一个基于JSON的RESTAPI

编辑:使用JsonNormalizer,我可以禁用一些属性,但是如果我想设置它们,而不需要递归性呢

基本上,我现在(工作)的是:

我想要的是:

{
    "tasks": [
        {
            "id": 1,
            "name": "first task",
            "category": {
                "id": 1,
                "name": "first category"
            }
        },
        {
            "id": 2,
            "name": "second task",
            "category": {
                "id": 1,
                "name": "first category"
            }
        }
    ]
}
我最初的问题是:

{
    "tasks": [
        {
            "id": 1,
            "name": "first task",
            "category": {
                "id": 1,
                "name": "first category",
                "tasks": [
                    {
                        "id": 1,
                        "name": "first task",
                        "category": {
                            "id": 1,
                            "name": "first category",
                            "tasks": [...] // infinite...
                        }
                    },
                    {
                        "id": 2,
                        "name": "second task",
                        "category": {
                            "id": 1,
                            "name": "first category",
                            "tasks": [...] // infinite...
                        }
                    }
                ]
            }
        },
        {
            "id": 2,
            "name": "second task",
            "category": {
                "id": 1,
                "name": "first category",
                "tasks": [
                    {
                        "id": 1,
                        "name": "first task",
                        "category": {
                            "id": 1,
                            "name": "first category",
                            "tasks": [...]
                        }
                    },
                    {
                        "id": 2,
                        "name": "second task",
                        "category": {
                            "id": 1,
                            "name": "first category",
                            "tasks": [...]
                        }
                    }
                ]
            }
        }
    ]
}
我有一个实体a,它与另一个实体B有多个关系

我实现了相反的一面,以便能够检索B one上的相关A实体。

class Task
{
    /**
     * @ORM\ManyToOne(targetEntity="List", inversedBy="task")
     * @ORM\JoinColumn(name="list_id", referencedColumnName="id")
     */
    private $list;

    public function toArray($recursive = false)
    {
        $entityAsArray = get_object_vars($this);

        if ($recursive) {
            foreach ($entityAsArray as &$var) {
                if ((is_object($var)) && (method_exists($var, 'toArray'))) {
                    $var = $var->toArray($recursive);
                }
            }
        }

        return $entityAsArray;
    }
}

use Doctrine\Common\Collections\ArrayCollection;

class List
{
    /**
     * @ORM\OneToMany(targetEntity="Task", mappedBy="list")
     */
    private $tasks;

    public function __construct()
    {
        $this->tasks = new ArrayCollection();
    }
}
然后我构建不同的API路由和控制器, 将输出呈现为JsonResponses, 对于给定的列表,我想使用路径呈现不同的任务:

/api/v1/lists/1/tasks
我的控制器的任务操作:

public function tasksAction($id)
{
    $em = $this->getDoctrine()->getManager();

    $list = $em->getRepository('MyRestBundle:List')->findOneActive($id);

    if (!$list) {
        throw $this->createNotFoundException('List undefined');
    }

    $tasks = $list->getTasks()->toArray();

    foreach ($tasks as &$task) {
        // recursively format the tasks as array
        $task = $task->toArray(true);
    }

    $serializer = $this->get('serializer');

    return $this->generateJsonResponse($serializer->normalize($tasks), 200);
}
但不幸的是,我总是遇到内存泄漏,因为toArray()的调用是递归的,所以每个任务都有一个list属性,其中包含一个tasks集合等

PHP致命错误:第146行src/Symfony/Component/Serializer/Serializer.PHP中允许的内存大小为134217728字节(尝试分配130968字节)

我想知道用Symfony2将具有关系的实体呈现为JSON对象的最干净的方法是什么

我真的必须在任务上循环才能执行“toArray()”方法吗

我也尝试过不使用它,但没有更多的成功,除了文件中的漏洞:src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php

我也尝试过不使用JMSSeralizer,内存泄漏被抛出到我自己的php文件中

当然,我可以增加内存限制,但由于这是toArray()调用的无限循环问题,因此它无法解决我的问题

如何正确格式化?

编辑

如果我理解您的代码在做什么,我认为toArray($recursive)函数会直接进入无限递归(只要$var=$this),也会间接进入无限递归(即,通过同级循环遍历自己的列表并再次调用原始任务的toArray)。尝试跟踪已处理的内容以防止无限递归:

/**
 * @ORM\ManyToOne(targetEntity="List", inversedBy="task")
 * @ORM\JoinColumn(name="list_id", referencedColumnName="id")
 */
private $list;
private $toArrayProcessed = array();

public function toArray($recursive = false)
{
    $this->toArrayProcessed[$this->getId()] = 1;

    $entityAsArray = get_object_vars($this);

    if ($recursive) {
        foreach ($entityAsArray as &$var) {
            if ((is_object($var)) && (method_exists($var, 'toArray')) && !isset($this->toArrayProcessed[$var->getId()]) {
                $var = $var->toArray($recursive);
            }
        }
    }

    return $entityAsArray;
}
编辑

如果我理解您的代码在做什么,我认为toArray($recursive)函数会直接进入无限递归(只要$var=$this),也会间接进入无限递归(即,通过同级循环遍历自己的列表并再次调用原始任务的toArray)。尝试跟踪已处理的内容以防止无限递归:

/**
 * @ORM\ManyToOne(targetEntity="List", inversedBy="task")
 * @ORM\JoinColumn(name="list_id", referencedColumnName="id")
 */
private $list;
private $toArrayProcessed = array();

public function toArray($recursive = false)
{
    $this->toArrayProcessed[$this->getId()] = 1;

    $entityAsArray = get_object_vars($this);

    if ($recursive) {
        foreach ($entityAsArray as &$var) {
            if ((is_object($var)) && (method_exists($var, 'toArray')) && !isset($this->toArrayProcessed[$var->getId()]) {
                $var = $var->toArray($recursive);
            }
        }
    }

    return $entityAsArray;
}

我有一种感觉,我们可能想得太多了。那对你有用吗

// In your controller

$repo = $this->getDoctrine()->getRepository('MyRestBundle:List');
$list = $repo->findActiveOne($id);

$tasks = $list->getTasks()->toArray();

$serializer = $this->get('serializer');
$json = $serializer->serialize($tasks, 'json');
为什么任务实体是递归的?一个任务不能包含另一个任务。只有列表才能包含任务数组。所以基本上我们要做的就是从列表实体中获取这个数组并序列化它。除非我错过了什么

编辑:

您可以要求序列化程序忽略以下内容中提到的某些属性:

尝试按照该示例,忽略
任务
实体中的
$list
属性

EDIT2:


您不需要在每个任务中都包含“列表”,因为它是相同的。您的json应该具有相同级别的“列表”和“任务”。然后,“任务”将是一个不包含“列表”的任务数组。要实现这一点,您可以使用类似于
数组('list'=>$list,'tasks'=>$tasks)
的东西,并将其序列化。

我觉得我们可能想得太多了。那对你有用吗

// In your controller

$repo = $this->getDoctrine()->getRepository('MyRestBundle:List');
$list = $repo->findActiveOne($id);

$tasks = $list->getTasks()->toArray();

$serializer = $this->get('serializer');
$json = $serializer->serialize($tasks, 'json');
为什么任务实体是递归的?一个任务不能包含另一个任务。只有列表才能包含任务数组。所以基本上我们要做的就是从列表实体中获取这个数组并序列化它。除非我错过了什么

编辑:

您可以要求序列化程序忽略以下内容中提到的某些属性:

尝试按照该示例,忽略
任务
实体中的
$list
属性

EDIT2:


您不需要在每个任务中都包含“列表”,因为它是相同的。您的json应该具有相同级别的“列表”和“任务”。然后,“任务”将是一个不包含“列表”的任务数组。要实现这一点,您可以使用类似于
array('list'=>$list,'tasks'=>$tasks)
的东西,并将其序列化。

这正是我的问题。不幸的是,此解决方案不起作用,因为属性$tasks的类型为“Doctrine\ORM\PersistentCollection”,它实现了“toArray()”方法,但没有ID。它可以与直接实体一起工作,但不能与实体数组一起工作!这正是我的问题。不幸的是,此解决方案不起作用,因为属性$tasks的类型为“Doctrine\ORM\PersistentCollection”,它实现了“toArray()”方法,但没有ID。它可以与直接实体一起工作,但不能与实体数组一起工作!我可以在实体列表中添加这样的getter,但为什么呢?我应该在任何地方叫它吗?我改变了上面的例子。也许这会更有用任务实体不是递归的。但我实现了列表和任务的反面。因此,任务是列表的一部分,列表可以包含一些任务。问题是,由于这个反面,关系是无限的。。。一个任务一个有任务的列表一个有任务列表的列表等等。我重试了你的方法,但它仍然不起作用,对不起。哦,真的谢谢,这是我完全错过的文档中的一点!因此,现在,当我移除与setIgnoredAttributes()的关系时,它至少工作得很好。但是如果我想要呢?如果你想要什么呢?你能详细说明一下吗?我可以在我的实体列表中添加这样一种getter,但为什么呢?我应该在任何地方叫它吗?我改变了上面的例子。也许这会更有用任务实体不是递归的。但我实现了列表和任务的反面。因此,任务是列表的一部分,列表可以包含一些任务。问题是这些关系是相互关联的