首页 > 解决方案 > Doctrine ORM / Symfony - 可以从父对象更新中更新子对象吗?

问题描述

我有两个实体,Quiz 和 Question with relationship OneToMany(1 Quiz 可以有很多问题)。

我正在尝试通过 RestApi 中的 put 操作更新测验对象(id=19),将问题对象的 2 个 id 添加到数组问题中。这些 id 直到那一刻都是孤儿,它们的 quiz_id 为空。

测验 id 19 更新前:

{
    "id": 19
    "alias": "Test Quiz",
    "questions": [],
    "hasFifty": false,
    "hasTip": false,
    "hasNext": false
}

Put 操作上的 Json 数据(更新测验对象 19):

 {
    "alias": "quiz-bill",
    "questions": [42,43],
    "hasFifty": true,
    "hasTip": true,
    "hasNext": true
}

put 请求的响应向我显示了更新测验对象:

 {
        "id": 19,
        "alias": "quiz-bill",
        "questions": [
            {
                "id": 42,
                "content": "test test test",
                "helpText": "dummy dummy dummy"                 
            },
            {
                "id": 43,
                "content": "test test",
                "helpText": "dummy"

            }
        ],
        "hasFifty": true,
        "hasTip": true,
        "hasNext": true
    }

但是这个对象是假的,当我从数据库中选择这些问题时,它们的 quiz_id 仍然为空。我想从父(测验)更新中更新这些子对象的父字段(quiz_id),但这似乎没有能力。

有没有人用学说和 Symfony 框架做过类似的事情?或者可以帮助我吗?

测验实体:

/**
 * Quiz.
 *
 * @ORM\Table(name="quiz")
 * @ORM\Entity(repositoryClass="ApiBundle\Repository\QuizRepository")
 * @JMS\ExclusionPolicy("all")
 */
class Quiz
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @JMS\Groups({"task", "quiz"})
     * @JMS\Expose
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="alias", type="string", length=255)
     * @JMS\Groups({"quiz"})
     * @JMS\Expose
     */
    private $alias;

    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Question", mappedBy="quiz")
     * @JMS\Groups({"quiz"})
     * @JMS\Expose
     */
    private $questions;

    /**
     * @var bool
     *
     * @ORM\Column(name="hasFifty", type="boolean", nullable=true)
     * @JMS\Groups({"quiz"})
     * @JMS\Expose
     */
    private $hasFifty;

    /**
     * @var bool
     *
     * @ORM\Column(name="hasTip", type="boolean", nullable=true)
     * @JMS\Groups({"quiz"})
     * @JMS\Expose
     */
    private $hasTip;

    /**
     * @var bool
     *
     * @ORM\Column(name="hasNext", type="boolean", nullable=true)
     * @JMS\Groups({"quiz"})
     * @JMS\Expose
     */
    private $hasNext;
/**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set alias.
     *
     * @param string $alias
     *
     * @return Quiz
     */
    public function setAlias($alias)
    {
        $this->alias = $alias;

        return $this;
    }

    /**
     * Get alias.
     *
     * @return string
     */
    public function getAlias()
    {
        return $this->alias;
    }

    /**
     * Set hasFifty.
     *
     * @param bool $hasFifty
     *
     * @return Quiz
     */
    public function setHasFifty($hasFifty)
    {
        $this->hasFifty = $hasFifty;

        return $this;
    }

    /**
     * Get hasFifty.
     *
     * @return bool
     */
    public function getHasFifty()
    {
        return $this->hasFifty;
    }

    /**
     * Set hasTip.
     *
     * @param bool $hasTip
     *
     * @return Quiz
     */
    public function setHasTip($hasTip)
    {
        $this->hasTip = $hasTip;

        return $this;
    }

    /**
     * Get hasTip.
     *
     * @return bool
     */
    public function getHasTip()
    {
        return $this->hasTip;
    }

    /**
     * Add question.
     *
     * @param \ApiBundle\Entity\Question $question
     *
     * @return Quiz
     */
    public function addQuestion(\ApiBundle\Entity\Question $question)
    {
        $this->questions[] = $question;

        return $this;
    }

    /**
     * Remove question.
     *
     * @param \ApiBundle\Entity\Question $question
     */
    public function removeQuestion(\ApiBundle\Entity\Question $question)
    {
        $this->questions->removeElement($question);
    }

    /**
     * Get questions.
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getQuestions()
    {
        return $this->questions;
    }

    /**
     * Set hasNext.
     *
     * @param bool $hasNext
     *
     * @return Quiz
     */
    public function setHasNext($hasNext)
    {
        $this->hasNext = $hasNext;

        return $this;
    }

    /**
     * Get hasNext.
     *
     * @return bool
     */
    public function getHasNext()
    {
        return $this->hasNext;
    }
}

问题实体:

/**
 * Question.
 *
 * @ORM\Table(name="question")
 * @ORM\Entity(repositoryClass="ApiBundle\Repository\QuestionRepository")
 * @JMS\ExclusionPolicy("all")
 */
class Question
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @JMS\Groups({"quiz" ,"question"})
     * @JMS\Expose
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="content", type="text")
     * @JMS\Groups({"quiz" ,"question"})
     * @JMS\Expose
     */
    private $content;

    /**
     * @var string
     *
     * @ORM\Column(name="help", type="text", nullable=true)
     * @JMS\Groups({"quiz" ,"question"})
     * @JMS\Expose
     */
    private $helpText;

    /**
     * @var \ApiBundle\Entity\Quiz
     *
     * @ORM\ManyToOne(targetEntity="Quiz", inversedBy="questions")
     * @ORM\JoinColumn(name="quiz_id", referencedColumnName="id")
     */
    protected $quiz;

    /**
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(name="createdAt", type="datetime")
     * @JMS\Groups({"quiz" ,"question"})
     * @JMS\Expose
     */
    private $createdAt;

    /**
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(name="updatedAt", type="datetime", nullable=true)
     * @JMS\Groups({"quiz" ,"question"})
     * @JMS\Expose
     */
    private $updatedAt;

    public function __construct()
    {
        $this->answers = new ArrayCollection();
    }

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

    /**
     * Set content.
     *
     * @param string $content
     *
     * @return Question
     */
    public function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get content.
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Set createdAt.
     *
     * @param \DateTime $createdAt
     *
     * @return Question
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt.
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt.
     *
     * @param \DateTime $updatedAt
     *
     * @return Question
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Get updatedAt.
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * Set helpText.
     *
     * @param string $helpText
     *
     * @return Question
     */
    public function setHelpText($helpText)
    {
        $this->helpText = $helpText;

        return $this;
    }

    /**
     * Get helpText.
     *
     * @return string
     */
    public function getHelpText()
    {
        return $this->helpText;
    }

    /**
     * Set quiz.
     *
     * @param \ApiBundle\Entity\Quiz $quiz
     *
     * @return Question
     */
    public function setQuiz(\ApiBundle\Entity\Quiz $quiz = null)
    {
        $this->quiz = $quiz;

        return $this;
    }

    /**
     * Get quiz.
     *
     * @return \ApiBundle\Entity\Quiz
     */
    public function getQuiz()
    {
        return $this->quiz;
    }


}

QuizController 放置动作:

/**
     * Update an existing Quiz.
     *
     * @param Request $request
     * @param int     $id
     *
     * @return mixed
     *
     * @Operation(
     *     tags={"Quiz"},
     *     summary="Update an existing Quiz.",
     *     @SWG\Response(
     *         response="204",
     *         description="Returned when an existing Quiz has been successful updated"
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Return when errors"
     *     ),
     *     @SWG\Response(
     *         response="401",
     *         description="Returned when access is not authorized"
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Return when not found"
     *     )
     * )
     *
     *
     * @Rest\View(serializerGroups={"quiz"})
     */
    public function putAction(Request $request, $id)
    {
        $quiz = $this->getDoctrine()->getRepository('ApiBundle:Quiz')->find($id);
        if (null === $quiz || empty($quiz)) {
            return new View(null, Response::HTTP_NOT_FOUND);
        }

        $form = $this->createForm(QuizType::class, $quiz, [
             'method' => 'PUT',
             'csrf_protection' => false,
         ]);
        $form->submit($request->request->all(), false);
        if (!$form->isValid()) {
            return $form;
        }

        $em = $this->getDoctrine()->getManager();
        $em->persist($quiz);
        $em->flush();

        return $quiz;
    }

测验类型表格:

<?php

namespace ApiBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class QuizType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
        ->add('alias')
        ->add('hasFifty')
        ->add('hasTip')
        ->add('hasNext')
        ->add('videoUrl')
        ->add('questions')
        ->add('task');
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'ApiBundle\Entity\Quiz',
            'csrf_protection' => false,
            'allow_extra_fields' => true,
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'apibundle_quiz';
    }
}

标签: symfonydoctrine-ormorm

解决方案


当您向测验添加已经保留的问题时,您不需要级联保留,但您仍然需要将测验设置为添加的问题:

// inside Quiz entity
public function addQuestion(\ApiBundle\Entity\Question $question)
{
    $question->setQuiz($this);
    $this->questions[] = $question;

    return $this;
}

这是因为(引用自Doctrine ORM Documentation

Doctrine 只会检查关联的拥有方是否有更改。

仅对关联的反面所做的更改将被忽略。确保更新双向关联的双方(或至少从 Doctrine 的角度来看拥有方)

在您的情况下,关联的反面是Quiz实体,拥有方是Question实体。


推荐阅读