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



我正在使用Symfony2.4与Doctrine2构建一个基于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": [...]
                        }
                    }
                ]
            }
        }
    ]
}

我有一个实体a,它与另一个实体b有一个多toone关系

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

class Task
{
    /**
     * @ORMManyToOne(targetEntity="List", inversedBy="task")
     * @ORMJoinColumn(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 DoctrineCommonCollectionsArrayCollection;
class List
{
    /**
     * @ORMOneToMany(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()是递归的,所以每个任务都有一个列表属性,其中有一个任务集合等。

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

我想知道什么是最干净的方式来呈现实体与关系作为JSON对象与Symfony2 ?

我真的需要循环我的任务来执行"toArray()"方法吗?

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

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

当然,我可以增加内存限制,但由于这是一个toArray()调用的无限循环问题,它不会解决我的问题。

我有一种感觉,我们可能想得太多了。这样行吗?

// 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可以包含任务数组。基本上我们要做的就是从List实体中获取这个数组并序列化它。除非我错过了什么。

编辑:

您可以要求序列化器忽略文档中提到的某些属性:

use SymfonyComponentSerializerSerializer;
use SymfonyComponentSerializerEncoderJsonEncoder;
use SymfonyComponentSerializerNormalizerGetSetMethodNormalizer;
$normalizer = new GetSetMethodNormalizer();
$normalizer->setIgnoredAttributes(array('age'));
$encoder = new JsonEncoder();
$serializer = new Serializer(array($normalizer), array($encoder));
$serializer->serialize($person, 'json'); // Output: {"name":"foo"}

试着遵循这个例子,只是忽略Task实体中的$list属性。

EDIT2:

你不需要'list'在每个任务中,因为它是相同的。你的json应该有'list'和'tasks'在同一级别。那么'tasks'将是一个不包含'list'的任务数组。为了达到这个目的,你可以设置array('list' => $list, 'tasks' => $tasks),并序列化它

EDIT

如果我理解你的代码在做什么,我认为toArray($recursive)函数直接(每当$var = $this)和间接(即通过兄弟姐妹迭代自己的列表并再次调用原始任务的toArray)进入无限递归。尝试跟踪已处理的内容以防止无限递归:

/**
 * @ORMManyToOne(targetEntity="List", inversedBy="task")
 * @ORMJoinColumn(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;
}

最新更新