Search code examples
javascriptphpformssymfonyformcollection

Form collection with last array element


I have two entities: Issue and Notes, and an issue can have multiple notes. I defined them like this:

class Issue {
  // ...

  /**
   * @ORM\OneToMany(targetEntity="Note", mappedBy="issue")
   */
  protected $notes;

  public function getNotes() {
    return $this->notes->toArray();
  }

  public function addNote($note) {
    if (!$this->notes->contains($note)) $this->notes->add($note);
  }

  public function removeNote($note) {
    if ($this->notes->contains($note)) $this->notes->removeElement($note);
  }
}

class Note {
  // ...

  /**
   * @ORM\Column(type="string", nullable=false)
   */
  protected $description;

  /**
   * @ORM\ManyToOne(targetEntity="Issue", inversedBy="notes")
   * @ORM\JoinColumn(name="issue", referencedColumnName="id", nullable=false)
   */
  protected $issue;

  public function getDescription() // ...

  public function setDescription() // ...

  public function getIssue() // ...

  public function setIssue() // ...
}

I defined an IssueType to create a form that embeds a NoteType form:

class IssueType extends AbstractType {
  public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        // ...
        ->add('notes', 'collection', ['type' => new NoteType()]);
  }
}

class NoteType extends AbstractType {
  public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->add('description', 'textarea');
  }
}

This works well when I want to create a new issue because the array of notes only contains one (blank) note created by the IssueController, which the user can fill in using the form. However, once the issue is created, I've got a view issue page that shows all the notes, and at the bottom there is a form to add a new note. The problem is that the collection form creates an input for a new (blank) note as well as for all the previous notes (see image below).

Unwanted description input boxes

Is there any way I can only include the input for the new (blank) note form using Symfony, or do I need to remove the previous notes with JavaScript?

EDIT:

Here's the code for the IssueController:

public function newAction(Request $request) {
  $em = $this->getDoctrine()->getManager();
  $issue = new Issue();
  $note = new Note();
  $issue->addNote($note);
  $note->setIssue($issue);

  $form = $this->createForm('issue', $issue);
  $form->handleRequest($request);

  // ...

  if ($form->isValid()) {
    $em->persist($issue);
    $em->persist($note);
    $em->flush();
    return $this->redirectToRoute(...);
  }

  return $this->render('/issue/new.html.twig', [
    'issue' => $issue,
    'form' => $form->createView()
  ]);
}

Solution

  • The note edit boxes appear because your form is instructing to create an editable collection for the one-to-many relationship. Putting something in a form type means it's typically going to be editable, or at the very least presented as form.

    If you want your form to only be able to ADD a new note, you must remove that collection property of the form.

    Instead of

    ->add('notes', 'collection', ['type' => new NoteType()]);
    

    have

    ->add('newNote', 'textarea', ['label' => 'Add note', 'mapped' => false];
    

    Your controller will need amendments too.

    public function newAction(Request $request) {
        $em = $this->getDoctrine()->getManager();
    
        $issue = new Issue();
        $form = $this->createForm('issue', $issue);
        $form->handleRequest($request);
    
        if ($form->isValid()) {
            if ($newNote = trim($form['newNote']->getData())) {
                $note = new Note();
    
                $issue->addNote($note);
                $note->setIssue($issue);
                $note->setDescription($newNote);
                $em->persist($note); // Probably not needed as you're cascading persist
            }
    
            $em->persist($issue);
    
            $em->flush();
            return $this->redirectToRoute(...);
        }
    
        return $this->render('/issue/new.html.twig', [
        'issue' => $issue,
        'form' => $form->createView()
        ]);
    }
    

    This will only show an input for a new note. To show existing notes you'll need to do this in your view, for example:

    <h2>Notes</h2>
    {% for note in issue.notes if note.description %}
        <p>{{ loop.index }}: {{ note.description }}</p>
    {% else %}
        <p>No notes have been added for this issue.</p>
    {% endfor %}