SonataAdminBundle embedded forms sonata_admin_type issue

heliogabal picture heliogabal · Jul 27, 2012 · Viewed 10.2k times · Source

I've got two entities, Quiz and QuizQuestion, with manyToMany one-sided relation. I'd like to embed questions form in quiz form. I'm on 2.0 branches. I am able to get sonata_type_model to work, getting list of id's in a dropdown, and working "add" button. However, I'm getting an error when trying to use sonata_type_admin:

Neither property "title" nor method "getTitle()" nor method "isTitle()" exists in class "Doctrine\ORM\PersistentCollection"
500 Internal Server Error - InvalidPropertyException

Here's my Quiz entity:

    <?php 

namespace Some\SiteBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;


/**
 * Some\SiteBundle\Entity\Quiz
 * @ORM\Table(name="quiz")
 * @ORM\Entity(repositoryClass="Some\SiteBundle\Entity\QuizRepository")

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



    /**
     * @ORM\Column(type="datetime", name="created_at")
     * 
     * @var DateTime $createdAt
     */
    protected $createdAt;


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


    /**
     * @var string $body
     *
     * @ORM\Column(name="body", type="text")
     */
    private $body;


  /**
    * @var QuizQuestion questions
     * @ORM\ManyToMany(targetEntity="QuizQuestion", cascade={"persist", "remove"} )
     **/
    protected $questions;


    public function __construct() {
        $this->questions = new ArrayCollection();
        $this->createdAt = new \DateTime();
    }


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

    /**
     * Set title
     *
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

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


    /**
     * Get quiz body.
     * 
     * @return string body
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Sets body
     * 
     * @param string $value body
     */
    public function setBody($body)
    {
        $this->body = $body;
    }



    /**
     * Gets an object representing the date and time the quiz was created.
     * 
     * @return DateTime A DateTime object
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }   



    /**
     * Add questions
     *
     * @param Some\SiteBundle\Entity\QuizQuestion $questions
     */
    public function addQuestion(\Some\SiteBundle\Entity\QuizQuestion $question)
    {
        $this->questions[] = $question;
    }

    /**
     * set question
     *
     * @param Some\SiteBundle\Entity\QuizQuestion $questions
     */
    public function setQuestion(\Some\SiteBundle\Entity\QuizQuestion $question)
    {
        foreach ($this->questions as $doc) {
            $this->questions->removeElement($doc);
        }
        $this->questions[] = $question;
    }

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


    /**
     * @ORM\PrePersist
     */
    public function beforePersist()
    {
        //$this->setCreatedAt(new \DateTime());        
        //$this->setModifiedAt(new \DateTime());
    }


      public function __toString()
  {
    return 'Quiz';
  }
}

And QuizQuestion entity:

<?php 

namespace Some\SiteBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;


/**
 * Some\SiteBundle\Entity\QuizQuestion
 * @ORM\Table(name="quiz_question")
 * @ORM\Entity
 */
class QuizQuestion
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;



    /**
     * @ORM\Column(type="datetime", name="created_at")
     * 
     * @var DateTime $createdAt
     */
    protected $createdAt;


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


    /**
     * @var string $body
     *
     * @ORM\Column(name="body", type="text")
     */
    private $body;

     /**
     * @var string $answer1
     *
     * @ORM\Column(name="answer1", type="text")
     */
    private $answer1;

     /**
     * @var string $answer2
     *
     * @ORM\Column(name="answer2", type="text")
     */
    private $answer2;

      /**
     * @var string $answer3
     *
     * @ORM\Column(name="answer3", type="text")
     */
    private $answer3;

     /**
     * @var string $answer4
     *
     * @ORM\Column(name="answer4", type="text")
     */
    private $answer4;

    /**
     * @var string $correctAnswer
     *
     * @ORM\Column(name="correct_answer", type="integer", length="1")
     */
    private $correctAnswer;




    public function __construct() {
        $this->createdAt = new \DateTime();
    }


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

    /**
     * Set title
     *
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

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


    /**
     * Get question body.
     * 
     * @return string body
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Sets body
     * 
     * @param string $value body
     */
    public function setBody($body)
    {
        $this->body = $body;
    }


    /**
     * Get question answer1.
     * 
     * @return string answer1
     */
    public function getAnswer1()
    {
        return $this->answer1;
    }

    /**
     * Sets answer1
     * 
     * @param string $value answer1
     */
    public function setAnswer1($answer1)
    {
        $this->answer1 = $answer1;
    }

        /**
     * Get question answer2.
     * 
     * @return string answer2
     */
    public function getAnswer2()
    {
        return $this->answer2;
    }

    /**
     * Sets answer2
     * 
     * @param string $value answer2
     */
    public function setAnswer2($answer2)
    {
        $this->answer2 = $answer2;
    }

        /**
     * Get question answer3.
     * 
     * @return string answer3
     */
    public function getAnswer3()
    {
        return $this->answer3;
    }

    /**
     * Sets answer3
     * 
     * @param string $value answer3
     */
    public function setAnswer3($answer3)
    {
        $this->answer3 = $answer3;
    }

        /**
     * Get question answer4.
     * 
     * @return string answer4
     */
    public function getAnswer4()
    {
        return $this->answer4;
    }

    /**
     * Sets answer4
     * 
     * @param string $value answer4
     */
    public function setAnswer4($answer4)
    {
        $this->answer4 = $answer4;
    }

     /**
     * Get question correctAnswer.
     * 
     * @return string correctAnswer
     */
    public function getCorrectAnswer()
    {
        return $this->correctAnswer;
    }

    /**
     * Sets answer1
     * 
     * @param string $value correctAnswer
     */
    public function setCorrectAnswer($correctAnswer)
    {
        $this->correctAnswer = $correctAnswer;
    }

    /**
     * Gets an object representing the date and time the question was created.
     * 
     * @return DateTime A DateTime object
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }   




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

And relevant admin classes. QuizAdmin first:

    <?php 

namespace Some\SiteBundle;

use Some\SiteBundle\Form\QuizQuestionType;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;

class QuizAdmin extends Admin
{


    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
              ->add('title', NULL, array('label' => 'tytuł:'))
              ->add('body', NULL, array('label' => 'opis:', 'required' => false, 'attr' => array(
                  'class' => 'tinymce', 'data-theme' => 'simple')
              ))
              ->add('questions', 'sonata_type_admin', array(), array('required' => false, 'edit' => 'inline'));
              //->add('categories', NULL, array('label' => 'kategorie:'))
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('title')
            ->add('body')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('id')
            ->add('title')
            ->add('_action', 'actions', array(
                'actions' => array(
                    'view' => array(),
                    'edit' => array(),
                )
            ))
            //->add('body')
        ;

    }

    public function validate(ErrorElement $errorElement, $object)
    {
        $errorElement
            ->with('title')
                ->assertMinLength(array('limit' => 2))
            ->end()
        ;
    }



}

And QuizQuestionAdmin:

    <?php 

namespace Some\SiteBundle;

use Some\SiteBundle\Form\QuizQuestionType;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;

class QuizAdmin extends Admin
{


    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
              ->add('title', NULL, array('label' => 'tytuł:'))
              ->add('body', NULL, array('label' => 'opis:', 'required' => false, 'attr' => array(
                  'class' => 'tinymce', 'data-theme' => 'simple')
              ))
              ->add('questions', 'sonata_type_admin', array(), array('required' => false, 'edit' => 'inline'));
              //->add('categories', NULL, array('label' => 'kategorie:'))
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('title')
            ->add('body')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('id')
            ->add('title')
            ->add('_action', 'actions', array(
                'actions' => array(
                    'view' => array(),
                    'edit' => array(),
                )
            ))
            //->add('body')
        ;

    }

    public function validate(ErrorElement $errorElement, $object)
    {
        $errorElement
            ->with('title')
                ->assertMinLength(array('limit' => 2))
            ->end()
        ;
    }



}

I tried to register quizQuestion as a service, but then I get Expected argument of type "Festus\SiteBundle\Entity\QuizQuestion", "Doctrine\ORM\PersistentCollection" given 500 Internal Server Error - UnexpectedTypeException

Couldn't find any solution after few hours of looking things up...

Answer

heliogabal picture heliogabal · Jul 27, 2012

Ok, I solved it replacing this:

->add('questions', 'sonata_type_admin', array(), array('required' => false, 'edit' => 'inline'));

with this:

->add('questions','collection', array( 'type' =>  new QuizQuestionType(),
                                          'allow_add' => true,
                                          'prototype' => true,
                                          'by_reference' => true,
                                          ));

but for me it looks rather like a workaround than solution :-|

Anyway, I checked bundles versions, symfony branch and found nothing incoherent, everything is on 2.0 branch.

PS - QuizQuestionType class, if anyone's interested... nothing fancy here, just regular form class:

<?php 

namespace Some\SiteBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class QuizQuestionType extends AbstractType
  {
      public function buildForm(FormBuilder $builder, array $options)
      {
    $builder
         ->add('title', NULL, array('label' => 'pytanie:'))
         ->add('body', NULL, array('label' => 'treść:', 'attr' => array(
          'class' => 'tinymce', 'data-theme' => 'simple')
         ))
         ->add('answer1', NULL, array('label' => 'odp. 1:'))
         ->add('answer2', NULL, array('label' => 'odp. 2:'))
         ->add('answer3', NULL, array('label' => 'odp. 3:'))
         ->add('answer4', NULL, array('label' => 'odp. 4:'))
         ->add('correctAnswer', NULL, array('label' => 'prawidłowa odp.:'))
    ;
    }

    public function getName()
    {
        return 'quiz_question';
    }

    public function getDefaultOptions(array $options){
        return array('data_class' => 'Some\SiteBundle\Entity\QuizQuestion');
    }
}