titanbird
titanbird

Reputation: 183

Symfony2, field type entity, "many-to-many" relation with extra fields, problems with the relation/ArrayCollection/multipe checkboxes in form

I want to create a form for a simple entry management. I got the entities Entry, EntryUser, and User. Tables in database: entry, entry_user and user.

I think i am close to be successful, but i got some problems here.

In my form, the author can check the users he want to add to the entry via checkboxes. Symfony/Doctrine should do the relation work for me and add rows into entry_user. One row for one selected checkbox.

Entry.php

/**
 * @ORM\Entity
 * @ORM\Table(name="entry")
 */
class Entry {

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

    /**
     * @ORM\OneToMany(targetEntity="EntryUser", mappedBy="entry", cascade={"all"}, orphanRemoval=true)
     */
   protected $users;

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

    /**
     * Add user
     *
     * @param User $user
     * @return $this
     *
     */
    public function addUser(User $user)
    {
        if (!$this->users->contains($user)) {
            $this->users->add($user);
        }

        return $this;
    }

    /**
     * Remove user
     *
     * @param User $user
     * @return $this
     */
    public function removeUser(User $user)
    {
        if ($this->users->contains($user)) {
            $this->users->removeElement($user);
        }

        return $this;
    }

    /**
    * @return array
    */
    public function getUsers()
    {
        return $this->users->toArray();
    }

    /**
    * Returns the true ArrayCollection of Users.
    * @return Doctrine\Common\Collections\ArrayCollection
    */
    public function getUsersCollection()
    {
        return $this->users;
    }

}

User.php

/**
 * @ORM\Entity
 * @ORM\Table(name="user")
 */
class User implements AdvancedUserInterface, EquatableInterface, \Serializable {

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

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

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

    /**
     * Set name
     *
     * @param string $name
     * @return User
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    // Many other methods for user sign in, roles and so on...

}

EntryUser.php

/**
 * @ORM\Entity
 * @ORM\Table(name="entry_user")
 */
class EntryUser {

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

    /**
     * @var integer
     *
     * @ORM\Column(name="user_id", type="integer", nullable=true)
     */
    protected $user_id;

    /**
     * @var integer
     *
     * @ORM\Column(name="entry_id", type="integer", nullable=true)
     */
    protected $entry_id;

    /**
     * @var User
     *
     * @ORM\ManyToOne(targetEntity="User", cascade={"persist"})
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
     */
    protected $user;

    /**
     * @var Entry
     *
     * @ORM\ManyToOne(targetEntity="Entry", inversedBy="users")
     * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="SET NULL")
     */
    protected $entry;


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

    /**
     * Set user_id
     *
     * @param integer $userId
     * @return EntryUser
     */
    public function setUserId($userId)
    {
        $this->user_id = $userId;

        return $this;
    }

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

    /**
     * Set entry_id
     *
     * @param integer $entryId
     * @return EntryUser
     */
    public function setEntryId($entryId)
    {
        $this->entry_id = $entryId;

        return $this;
    }

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

    /**
     * Set user
     *
     * @param User $user
     * @return EntryUser
     */
    public function setUser(User $user = null)
    {
        $this->user = $user;

        return $this;
    }

    /**
     * Get user
     *
     * @return User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * Set entry
     *
     * @param Entry $entry
     * @return EntryUser
     */
    public function setEntry(Entry $entry = null)
    {
        $this->entry = $entry;

        return $this;
    }

    /**
     * Get entry
     *
     * @return Entry
     */
    public function getEntry()
    {
        return $this->entry;
    }

}

I use ManyToOne relationships here because i want to use a file named EntryUser.php to add some custom fields later. I need this because i must store some additional data there in entry_user.

Because:

And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.

A <--- Many to many with field ---> B

would become

A --One to many--> C (with new fields) <-- One to many--B

and of course, C has ManyToOne relationships with both A and B.

See the comments in: ManyToMany relationship with extra fields in symfony2 orm doctrine

My EntryType.php form definition includes the following, to create the checkboxes for the template:

$builder->add('users', 'entity', array(
    'class' => 'MyCompMyAppBundle:User',
    'multiple' => true,
    'expanded' => true,
    'property' => 'name',
    'label' => 'Freunde',
    'query_builder' => function(EntityRepository $er) {

        return $er->createQueryBuilder('u')->select('a')
            ->from('MyComp\MyAppBundle\Entity\User', 'a')
            ->where('EXISTS (
                SELECT b
                FROM MyComp\MyAppBundle\Entity\UserFriend b
                WHERE b.created_by = :my_user_id
                AND b.friend_user_id = a.id
            )')
            ->andWhere('EXISTS (
                SELECT c
                FROM MyComp\MyAppBundle\Entity\UserFriend c
                WHERE c.created_by = a.id
                AND c.friend_user_id = :my_user_id
            )')
           ->setParameter('my_user_id', $this->user->getId());

    },
    'required' => true,
));

As you can see, i load User objects here for the form field (type: entity). UserFriend is another entity (table: user_friend). A friend list is saved there. Here all the friends gets loaded. They will be shows as the checkboxes.

Now, if i go to my form in my browser, and check some users for the entry und if i submit the form, i get this error:

ORMException: Found entity of type MyComp\MyAppBundle\Entity\User on association MyComp\MyAppBundle\Entity\Entry#friends, but expecting MyComp\MyAppBundle\Entity\EntryUser

So it is very confusing. How can i make this work?

How can i make symfony and doctrine work to insert data automatically into entry and entry_user?

Important: I want to make it possible that the author can edit the entry later. So the checkboxes should be selected by default as it is saved in entry_user.

I just try to understand this and got a little bit confused. But i dont know how to make this work.

Upvotes: 1

Views: 7081

Answers (2)

Matthias Kleine
Matthias Kleine

Reputation: 1235

For this case you should check "form collections" in Symfony: http://symfony.com/doc/current/cookbook/form/form_collections.html

With this technique you will add a form type to crate a single "EntryUser" and after that you can add a collection of that form to the parent form. Quite easy and well explained in the liked article.

Upvotes: 0

titanbird
titanbird

Reputation: 183

I found an awesome solution in the web. I was searching for many days now, but this guy made my day! :-)

You can read how it works and learn from an awesome example here!

Happy coding!

Upvotes: 4

Related Questions