带有FOSRestBundle的Symfony API:已检测到循环引用

时间:2017-06-23 14:12:53

标签: api symfony serialization entity fosrestbundle

我正在开发一个symfony项目来构建一个rest API,我有4个相互关联的实体:

class diagram

当我想获取资源时,我已经安装了FOSRestBundle来构建一个GET Web服务:

  

http://evaluation.dev/app_dev.php/api/families

我收到了这个错误:

  

消息:“已检测到循环引用(已配置   限制:1)“,

这是我的控制器:

<?php

namespace API\APIBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;

class CartographyController extends Controller
{
    /**
     * @Rest\View()
     * @Rest\Get("/posts")
     * @param Request $request
     * @return Response
     */
    public function getPostsAction(Request $request)
    {
        $encoder = new JsonEncoder();
        $normalizer = new GetSetMethodNormalizer();

        $serializer = new Serializer(array($normalizer), array($encoder));
        $posts = $this->get('doctrine.orm.entity_manager')
            ->getRepository('EvalBundle:Post')
            ->findAll();

        return new Response($serializer->serialize($posts, 'json'));

    }

    /**
     * @Rest\View()
     * @Rest\Get("/employments")
     * @param Request $request
     * @return Response
     */
    public function geEmploymentAction(Request $request)
    {
        $encoder = new JsonEncoder();
        $normalizer = new GetSetMethodNormalizer();

        $serializer = new Serializer(array($normalizer), array($encoder));
        $employments = $this->get('doctrine.orm.entity_manager')
            ->getRepository('EvalBundle:Employment')
            ->findAll();
        return new Response($serializer->serialize($employments, 'json'));

    }

    /**
     * @Rest\View()
     * @Rest\Get("/professions")
     * @param Request $request
     * @return Response
     */
    public function geProfessionsAction(Request $request)
    {
        $encoder = new JsonEncoder();
        $normalizer = new GetSetMethodNormalizer();

        $serializer = new Serializer(array($normalizer), array($encoder));
        $professions = $this->get('doctrine.orm.entity_manager')
            ->getRepository('EvalBundle:Profession')
            ->findAll();

        return new Response($serializer->serialize($professions, 'json'));    }

    /**
     * @Rest\View()
     * @Rest\Get("/families")
     * @param Request $request
     * @return Response
     */
    public function geFamiliesAction(Request $request)
    {
        $encoder = new JsonEncoder();
        $normalizer = new GetSetMethodNormalizer();

        $serializer = new Serializer(array($normalizer), array($encoder));
        $families = $this->get('doctrine.orm.entity_manager')
            ->getRepository('EvalBundle:Family')
            ->findAll();

        return new Response($serializer->serialize($families, 'json'));
    }
}

家庭实体:

<?php

namespace EvalBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * Family
 *
 * @ORM\Table(name="family")
 * @ORM\Entity(repositoryClass="EvalBundle\Repository\FamilyRepository")
   */
class Family
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;

    /**
     * One Family has Many Professions.
     * @ORM\OneToMany(targetEntity="Profession", mappedBy="family",orphanRemoval=true,cascade={"persist", "remove"},fetch="EAGER")
     */
    protected  $professions;



    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Family
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getProfessions()
    {
        return $this->professions;
    }

    /**
     * @param mixed $professions
     */
    public function setProfessions($professions)
    {
        $this->professions = $professions;
    }
    public function __toString() {
        return $this->name;
    }
}

专业实体:

<?php

namespace EvalBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Profession
 *
 * @ORM\Table(name="profession")
 * @ORM\Entity(repositoryClass="EvalBundle\Repository\ProfessionRepository")
 */
class Profession
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;

    /**
     * Many professions have One Family.
     * @ORM\ManyToOne(targetEntity="Family", inversedBy="professions",cascade={"persist", "remove"})
     */
    public $family;

    /**
     * One Profession has Many Employment.
     * @ORM\OneToMany(targetEntity="Employment", mappedBy="profession",cascade={"persist", "remove"}, orphanRemoval=true,fetch="EAGER")
     */
    private $employments;

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Profession
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getEmployments()
    {
        return $this->employments;
    }

    /**
     * @param mixed $employments
     */
    public function setEmployments($employments)
    {
        $this->employments = $employments;
    }

    /**
     * @return mixed
     */
    public function getFamily()
    {
        return $this->family;
    }

    /**
     * @param mixed $family
     */
    public function setFamily($family)
    {
        $this->family = $family;
    }

    public function __toString() {
        return $this->name;
    }
}

发布实体

<?php

namespace EvalBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Post
 *
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="EvalBundle\Repository\PostRepository")
 */
class Post
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;

    /**
     * Many Posts have One Employment.
     * @ORM\ManyToOne(targetEntity="Employment", inversedBy="posts", fetch="EAGER")
     * @ORM\JoinColumn(name="employment_id", referencedColumnName="id",nullable=false)
     */
    public $employment;

    /**
     * One Post has Many LevelRequired.
     * @ORM\OneToMany(targetEntity="RequiredLevel", mappedBy="post")
     */
    private $requiredLevels;

    /**
     * One Post has Many PostEvaluation.
     * @ORM\OneToMany(targetEntity="PostEvaluation", mappedBy="post")
     */
    private $postEvaluations;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Post
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getEmployment()
    {
        return $this->employment;
    }


    public function getProfession(){
        return $this->employment->profession;
    }

    public function getFamily(){
        return $this->employment->profession->family;
    }


    /**
     * @param mixed $employment
     */
    public function setEmployment($employment)
    {
        $this->employment = $employment;
    }

    public function __toString() {
        return $this->name;
    }
}

就业实体

<?php

namespace EvalBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Employment
 *
 * @ORM\Table(name="employment")
 * @ORM\Entity(repositoryClass="EvalBundle\Repository\EmploymentRepository")
 */
class Employment
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;

    /**
     * Many Employments have One Profession.
     * @ORM\ManyToOne(targetEntity="Profession", inversedBy="employments",fetch="EAGER")
     */
    public $profession;

    /**
     * One Employment has Many post.
     * @ORM\OneToMany(targetEntity="Post", mappedBy="employment",cascade={"persist", "remove"}, orphanRemoval=true,cascade={"persist", "remove"})
     */
    private $posts;

    /**
     * One Employment has One Grid.
     * @ORM\OneToOne(targetEntity="Grid", mappedBy="employment")
     */
    private $grid;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Employment
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getProfession()
    {
        return $this->profession;
    }

    /**
     * @param mixed $profession
     */
    public function setProfession($profession)
    {
        $this->profession = $profession;
    }

    /**
     * @return mixed
     */
    public function getPosts()
    {
        return $this->posts;
    }

    /**
     * @param mixed $posts
     */
    public function setPosts($posts)
    {
        $this->posts = $posts;
    }

    /**
     * @return mixed
     */
    public function getGrid()
    {
        return $this->grid;
    }

    /**
     * @param mixed $grid
     */
    public function setGrid($grid)
    {
        $this->grid = $grid;
    }
    public function __toString() {
        return $this->name;
    }
}

请问任何解决方案?

1 个答案:

答案 0 :(得分:2)

对于您的示例,您可以像这样避免使用CircularReference

$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceLimit(1);

$normalizer->setCircularReferenceHandler(function ($object) {
    return $object->getName();
});

$serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
var_dump($serializer->serialize($org, 'json'));

但在您的示例中,您不能将FOSRestBundle用于控制器中的视图。 FOSRestController为您提供handleView()和view()方法。喜欢这个

class CartographyController extends FOSRestController 
{
    public function getPostsAction(Request $request)
    {
        $posts = $this->get('doctrine.orm.entity_manager')
            ->getRepository('EvalBundle:Post')
            ->findAll();
        $view = $this->view($posts, 200);

        return $this->handleView($view);
    }

在这种情况下,序列化程序是一项服务,此服务在config.yml中激活:

在你的app / config / config.yml

framework:
    serializer: { enabled: true }

为了避免使用circularReference,您可以执行此操作。 在你的app / config / services.yml

circular_reference_handler:
    public: false
    class: callback
    factory: [AppBundle\Serializer\CircularHandlerFactory, getId]
serializer.normalizer.object:
    class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
    arguments: ["@serializer.mapping.class_metadata_factory", null, "@serializer.property_accessor"]
    public: false
    tags: [serializer.normalizer]
    calls:
        - method: setCircularReferenceLimit
          arguments: [1]
        - method: setCircularReferenceHandler
          arguments: ["@circular_reference_handler"]

工厂可以是这样的:

namespace AppBundle\Serializer;

class CircularHandlerFactory
{
    /**
     * @return \Closure
     */
    public static function getId()
    {
        return function ($object) {
            return $object->getId();
        };
    }
}

另一个想法是将GROUPS用于序列化器。 FosRestBundle给出了一个注释: @Rest \视图(serializerGroups = {&#34;用户&#34;})

更多信息: https://symfony.com/doc/current/serializer.html#using-serialization-groups-annotations Symfony2, FOSRestBundle. How to use group with JMSSerializerBundle?