渲染Doctrine实体,包括递归关系为JSON

时间:2014-05-13 18:21:37

标签: json rest symfony doctrine-orm entity-relationship

我正在使用带有Doctrine2的Symfony2.4构建基于JSON的REST API。

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

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

{
    "tasks": [
        {
            "id": 1,
            "name": "first task"
        },
        {
            "id": 2,
            "name": "second task"
        }
    ]
}

我想要的是:

{
    "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": [...]
                        }
                    }
                ]
            }
        }
    ]
}

我有一个与另一个实体B有很多关系的实体A.

我已经实现了反面,以便能够检索B上的相关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属性,它有一个任务集合等。

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

我想知道将具有关系的实体作为JSON对象与Symfony2一起呈现的最简洁方法是什么?

我是否真的必须循环执行我的任务来执行" toArray()"方法?

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

我也尝试过没有JMSSeralizer,并且在我自己的php文件中抛出了内存泄漏。

当然,我可以增加内存限制,但因为它是toArray()调用的无限循环问题,所以它不会解决我的问题。

如何正确格式化?

2 个答案:

答案 0 :(得分:3)

我有一种感觉,我们可能会过度思考这一点。这对你有用吗?

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

为什么Task Entity是递归的?任务不能包含其他任务。只有List才能包含一系列任务。所以基本上我们应该做的就是从List实体获取这个数组并将其序列化。除非我遗漏了什么。

修改

您可以要求序列化程序忽略documentation

中提到的某些属性
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;

$normalizer = new GetSetMethodNormalizer();
$normalizer->setIgnoredAttributes(array('age'));
$encoder = new JsonEncoder();

$serializer = new Serializer(array($normalizer), array($encoder));
$serializer->serialize($person, 'json'); // Output: {"name":"foo"}

尝试按照该示例操作,只需忽略$list实体中的Task属性。

<强> EDIT2:

你不需要在每个任务中使用'list',因为它是相同的。你的json应该在同一级别有'list'和'tasks'。然后'任务'将是一系列不包含'list'的任务。要实现这一目标,您可以使用array('list' => $list, 'tasks' => $tasks)之类的内容并序列化。

答案 1 :(得分:1)

修改

如果我理解你的代码在做什么,我认为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;
}