8

Suppose I have two entities: a post and a comment. Each post can have many comments. Now, suppose I have a comment form. It is supposed to take user input and store it in the database.

Simple stuff. At least, it should be, but I can't get it to work.

How do I refer to the post (parent) when creating the comment (child)? I tried manually passing the post_id to the comment form as a hidden field, but received an error complaining about how the post ID is a string.

Expected argument of type "App\Entity\Post or null", "string" given.

Here is my code so far. Can someone nudge me into the right direction?

CommentType.php

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $post_id = $options['post_id'];

    $builder->add('content', TextareaType::class, [
        'constraints' => [
            new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
            new Assert\Length([
                'min'        => 10,
                'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
            ]),
        ],
    ])->add('post', HiddenType::class, ['data' => $post_id]);
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => Comment::class,
        'post_id' => NULL,
    ]);
}

PostController.php (this is where the comment form appears)

// Generate the comment form.
$comment = new Comment();
$form = $this->createForm(CommentType::class, $comment, [
    'action' => $this->generateUrl('new_comment'),
    'post_id'   => $post_id,
]);

CommentController.php

/**
 * @param Request $request
 * @Route("/comment/new", name="new_comment")
 * @return
 */
public function new(Request $request, UserInterface $user)
{
    // 1) Build the form
    $comment = new Comment();
    $form = $this->createForm(CommentType::class, $comment);

    // 2) Handle the submit (will only happen on POST)
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid())
    {
        // 3) Save the comment!
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($comment);
        $entityManager->flush();
    }

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

Thank you very much for your help!

Nathanael
  • 6,893
  • 5
  • 33
  • 54
  • 1
    As mentioned in error message you need to pass the entity object of your post_id. – Ben Nov 04 '18 at 23:39

4 Answers4

4

You just need to pass the actual Post entity, not just the id. Try this:

CommentController.php

public function new(Request $request, UserInterface $user, Post $post)
{
    // 1) Build the form
    $comment = new Comment();
    $comment->setPost($post); //where $post is instance of App\Entity\Post
    $form = $this->createForm(CommentType::class, $comment);

    // 2) Handle the submit (will only happen on POST)
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid())
    {
        // 3) Save the comment!
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($comment);
        $entityManager->flush();
    }

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

CommentType

public function buildForm(FormBuilderInterface $builder, array $options)
{
    //don't need to set the $post here

    $builder->add('content', TextareaType::class, [
        'constraints' => [
            new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
            new Assert\Length([
                'min'        => 10,
                'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
            ]),
        ],
    ]);
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => Comment::class
         //don't need the default here either
     ]);
}

Comment Entity

class Comment 
{
  /** 
  * @ORM\ManyToOne(targetEntity="App\Entity\Post")
  */
  private $post;

  //other vars

  public function setPost(\App\Entity\Post $post): void
  {
    $this->post = $post;
  }

  public function getPost(): \App\Entity\Post 
  {
     return $this->post;
  }

  //other functions
}
flint
  • 345
  • 5
  • 15
  • Thanks for the help. However, this does not work. `$post_id` is undefined in CommentType, and if I define it as a `Post` rather than an ID (string/int), I get a type conversion error. – Nathanael Nov 12 '18 at 18:23
  • Also, I do not have a `PostInterface`, unless there is something happening behind the scenes there that I do not understand. – Nathanael Nov 12 '18 at 18:31
  • Using an interface is best practice but you don't have to, you can typehint the entity. I've amended my answer to show the comment class as well. See more here: https://symfony.com/doc/current/doctrine/associations.html – flint Nov 12 '18 at 19:42
  • Thank you, but my comment entity is already set up this way. The issue lies (I believe) in this line: `->add('post', HiddenType::class, ['data' => $post_id]);`. $post_id does not exist. – Nathanael Nov 18 '18 at 17:03
  • You don't need that line, I've corrected the answer. You are setting the post with $comment->setPost($post); – flint Nov 19 '18 at 12:00
  • Thanks for all of your help. However, it's *still* not working. I get this error: `"Unable to guess how to get a Doctrine instance from the request information for parameter "post".` If I remove the $post parameter and instead manually create a Post object, I get this error instead: `A new entity was found through the relationship 'App\Entity\Comment#post' that was not configured to cascade persist operations for entity [...]`. – Nathanael Nov 22 '18 at 01:12
  • At this point, I may just remove the Doctrine relationship between the two tables and manually set an ID. I'd rather learn how to do this properly though. – Nathanael Nov 22 '18 at 01:12
1

This code works for me:

CommentController.php

As suggested by flint above, you just need to pass the actual Post entity, not just the id. Then if you have this error "Unable to guess how to get a Doctrine instance from the request information for parameter "post" this is because you need to add the post slug in the path of the new_comment route. The ParamConverter is called implicitly and it need this slug {post} with the same name as the name you used for the post parameter in the function.

/**
 * @param Request $request
 * @return \Symfony\Component\HttpFoundation\RedirectResponse
 * @Route("/comment/new/{post}", name="new_comment")
 */
public function new(Request $request, Post $post)
{
    $comment = new Comment();
    $comment->setPost($post); //where $post is instance of App\Entity\Post
    $form = $this->createForm(CommentType::class, $comment);

    // 2) Handle the submit (will only happen on POST)
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid())
    {
        // 3) Save the comment!
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($comment);
        $entityManager->flush();
    }

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

PostController.php

/**
 * @Route("/post/{id}", name="get_post")
 */
public function getPostAction(Post $post)

{
    // Generate the comment form.
    $comment = new Comment();
    $form = $this->createForm(CommentType::class, $comment, [
        'action' => $this->generateUrl('new_comment', ['post' => $post->getId()]),
    ]);

    return $this->render('listeArticles.html.twig', [
        'form' => $form->createView()
    ]);

 }

CommentType.php

class CommentType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        //don't need to set the $post here

        $builder
            ->add('content', TextareaType::class, [
            'constraints' => [
                new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
                new Assert\Length([
                    'min'        => 10,
                    'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
                ]),
            ],
        ])
        ->add('submit', SubmitType::class);
    }

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

With this you don't need to remove the Doctrine relationship between the two tables and manually set an ID.

fgamess
  • 1,276
  • 2
  • 13
  • 25
0

Dont put in to form field, for exampled

public function new(Request $request, UserInterface $user)
{
    // 1) Build the form
    $comment = new Comment();
    $form = $this->createForm(CommentType::class, $comment);

    // 2) Handle the submit (will only happen on POST)
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid())
    {
        comment->setPostId($post_id)
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($comment);
        $entityManager->flush();
    }

    return $this->redirectToRoute('homepage');
}
0

The error message says it all:

Expected argument of type "App\Entity\Post or null", "string" given.

If you go to your comment Entity (App\Entity\Comment) you'll see that your class refers to the parent post as a Post Class (App\Entity\Post) and not as a "post_id".

It is the ORM (doctrine in this case) who does the link in your physical database and your Entity classes and add a post_id field in your table.

This is the what ORM (Object Relational Model) is for. You should no more consider Post and Comment as Sql tables but as Classes (OOP).

Thus is I want to add a comment related to someParent I should do something like:

$comment = new Comment();
$comment->setPost($post);

Where $post is an instance of the class Post.

elkolotfi
  • 5,645
  • 2
  • 15
  • 19