简体   繁体   中英

Symfony 4 JSON nested entities through form

I am trying to submit a JSON through a complex form. I can't figure out what I am missing. The "normal" form is functioning. I am able to get serialized data with groups.

Class TaskBoard

    class TaskBoard
{
    /**
     * @var integer $id id
     * 
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var \DateTime $createdTime createdTime
     * 
     * @ORM\Column(type="datetime")
     * @Assert\DateTime()
     */
    private $createdTime;

    /**
     * @var \DateTime $lastUpdatedTime lastUpdatedTime
     * 
     * @ORM\Column(type="datetime", nullable=true)
     * @Assert\DateTime()
     */
    private $lastUpdatedTime;

    /**
     * @var string $name name
     * 
     * @ORM\Column(type="string", length=20)
     * @Assert\Type("string")
     * @Assert\NotBlank()
     * @Assert\Length(
     *      min = 2,
     *      max = 20,
     *      minMessage = "The name must be at least {{ limit }} characters long",
     *      maxMessage = "The name cannot be longer than {{ limit }} characters"
     * )
     */
    private $name;

    /**
     * @var App\Entity\User $user user
     * 
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="taskboards", cascade={"persist"})
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     */
    private $user;

    /**
     * @var string $description description
     * 
     * @ORM\Column(type="text", nullable=true)
     * @Assert\Type("string")
     */
    private $description;

    /**
     * @var App\Entity\Status $status status
     * 
     * @ORM\ManyToOne(targetEntity="App\Entity\Status", inversedBy="taskboards", cascade={"persist"})
     * @ORM\JoinColumn(name="status_id", referencedColumnName="id", nullable=false)
     */
    private $status;

    /**
     * @var boolean $completed completed
     * 
     * @ORM\Column(type="boolean")
     */
    private $completed;

    /**
     * @var \DateTime $deadLine deadLine
     * 
     * @ORM\Column(type="date", nullable=true)
     * @Assert\DateTime()
     * @Assert\GreaterThanOrEqual("today")
     */
    private $deadLine;

Class Status

class Status
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=20)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\TaskBoard", mappedBy="status", cascade={"persist"})
     */
    public $taskboards;

Class User

class User extends BaseUser
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    protected $id;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\TaskBoard", mappedBy="user", cascade={"persist"})
     */
    public $taskboards;

Form

class TaskBoardType extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
                ->add('name', TextType::class)
                ->add('user', EntityType::class, array(
                    'class' => User::class,
                    'choice_label' => 'username',
                        )
                )
                ->add('description', TextareaType::class, array(
                    'required' => false
                ))
                ->add('status', EntityType::class, array(
                    'class' => Status::class,
                    'choice_label' => 'name',
                        )
                )
                ->add('deadLine', DateTimeType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults([
            'data_class' => TaskBoard::class,
            'csrf_protection' => false,
        ]);
    }

Controller

class TaskBoardAPIController extends AbstractController {

    public function postTaskBoard(Request $request) {

        $form = $this->createForm(TaskBoardType::class, new TaskBoard());

        $data = json_decode(
                $request->getContent(), true
        );

        var_dump($data);

        $form->submit($data);

        if (!$form->isValid()) {
            return new JsonResponse(
                    [
                'status' => 'error',
                'errors' => $form->getErrors(),
                'form' => $form,
                    ], JsonResponse::HTTP_BAD_REQUEST
            );
        }

        $this->entityManager->persist($form->getData());
        $this->entityManager->flush();

        return new JsonResponse(
                [
            'status' => 'ok',
                ], JsonResponse::HTTP_CREATED
        );
    }

JSON sent

{
    "name": "XXX",
    "user": {
        "id": 1,
        "username": "BFA"
    },
    "description": "XXXXXXXXXXXXXXX",
    "status": {
        "id": 1,
        "name": "To Do"
    },
    "completed": false
}

The form is not valid and blank in the JsonResponse.

I based myself on : https://codereviewvideos.com/course/beginners-guide-back-end-json-api-front-end-2018/video/symfony-4-json-api-form-submission and Deserialize an entity with a relationship with Symfony Serializer Component

Thanks for your help.

Your forgot to handle and get form submission. Note that you will need to get request data using something like

    $form = $this->createFormBuilder($task)
        ... add fields
        ->getForm();

    $form->handleRequest($request); // handling request

    if ($form->isSubmitted() && $form->isValid()) {
        // $form->getData() holds the submitted values
        // but, the original `$task` variable has also been updated
        $task = $form->getData();

        // ... perform some action, such as saving the task to the database
        // for example, if Task is a Doctrine entity, save it!
        // $entityManager = $this->getDoctrine()->getManager();
        // $entityManager->persist($task);
        // $entityManager->flush();

        return $this->redirectToRoute('...');
    }

for more, see Handling Form Submissions

What was wrong was the JSON input.

The form does this :

class TaskBoardType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
            ->add('name', TextType::class)
            ->add('user', EntityType::class, array(
                'class' => User::class,
                'choice_label' => 'username',
                    )
            )
            ->add('description', TextareaType::class, array(
                'required' => false
            ))
            ->add('status', EntityType::class, array(
                'class' => Status::class,
                'choice_label' => 'name',
                    )
            )
            ->add('deadLine', DateTimeType::class)
    ;
}

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults([
        'data_class' => TaskBoard::class,
        'csrf_protection' => false,
    ]);
}

When checking the code generated from the form, this is the result :

<div>
   <label for="task_board_user" class="required">User</label>
   <select id="task_board_user" name="task_board[user]">
      <option value="1">XXX</option>
      <option value="2">XXX</option>
   </select>
</div>

Thus the form is expecting directly an INT/ID.

By changing the JSON as follow it goes through validation :

{
    "name": "XXXO",
    "user": 1,
    "description": "XXXXXXXXXXXXXXX",
    "status": 1
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM