Fixtures with a self-referencing many to many relation

1.1k views Asked by At

I have an User class and I am trying to implement friends relationships between users. I have followed Doctrine's documentation and I think that the relationship is fine. The problem I have is that I can't make a user fixture because I haven't add the reference first.

This is the error message:

[Symfony\Component\Debug\Exception\ContextErrorException]
  Notice: Undefined index: user-2 in C:\Programming\xampp\htdocs\myProject
  \vendor\doctrine\data-fixtures\lib\Doctrine\Common\DataFixtures\ReferenceRe
  pository.php line 145

And this is my User.php:

namespace MyProject\UserBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use Symfony\Component\Validator\ExecutionContextInterface;

/**
 * @ODM\Document(db="projectdb", collection="users")
 * @ODM\Document(repositoryClass="MyProject\UserBundle\Document\UserRepository")
 * @DoctrineAssert\UniqueEntity("email")
 */
class User implements UserInterface {
    /**
     * @ODM\Id
     */
    private $id;

    /**
     * @ODM\String
     * @Assert\NotBlank(groups={"signUp"})
     * @Assert\Length(min = 6)
     */
    private $password;

    /**
     * @ODM\String
     * @Assert\Email()
     */
    private $email;

    /**
     * @ODM\ReferenceMany(targetDocument="User", mappedBy="myFriends")
     */
    private $friendsWithMe;

    /**
     * @ODM\ReferenceMany(targetDocument="User", inversedBy="friendsWithMe")
     */
    private $myFriends;

    public function __construct()
    {
        $this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
        $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
    }    

    public function getId()
    {
        return $this->id;
    }

    public function setPassword($password)
    {
        $this->password = $password;
        return $this;
    }    

    public function getPassword()
    {
        return $this->password;
    }    

    public function setEmail($email)
    {
        $this->email = $email;
        return $this;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function getFriendsWithMe()
    {
        return $this->friendsWithMe;
    }

    public function getMyFriends()
    {
        return $this->myFriends;
    }

    public function addFriend(User $user)
    {
        $user->friendsWithMe[] = $this;
        $this->myFriends[]     = $user;
    }

    public function getRoles() {
        return array("ROLE_USER");
    }

    public function eraseCredentials() {

    }

}

And this is Users.php (fixtures):

namespace Filmboot\MovieBundle\DataFixtures\MongoDB;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

use Filmboot\UserBundle\Document\User;


class Users extends AbstractFixture implements OrderedFixtureInterface {
    public function load(ObjectManager $manager) {
        $users = array(
            array(
                "password"        => "123456a",
                "email"           => "[email protected]",
                "friendsWithMe"   => [$this->getReference("user-2")],
                "myFriends"       => ""),
            array(
                "password"        => "123456b",
                "email"           => "[email protected]",
                "friendsWithMe"   => "",
                "myFriends"       => [$this->getReference("user-1")])
        );

        foreach($users as $i => $user) {
            $i++;
            $document = new User();
            $document->setPassword($user["password"]);
            $document->setEmail($user["email"]);

            foreach ($user["myFriends"] as $friend) {
                $document->addFriend($friend);
            }

            $manager->persist($document);
            $this->addReference("user-" . $i, $document);

        }

        $manager->flush();
    }

    public function getOrder() {
        return 0;
    }
}
2

There are 2 answers

0
xaaleja On BEST ANSWER

I have found the solution to my problem. I have removed inversedBy from $myFriends and mappedBy from $friendsWithMe. Then, in fixtures first I add all the users, and secondly I get each user to add his friends.

foreach($users as $i => $user) {
    $i++;
    $ref = $this->getReference($i);
    foreach($user['myFriends'] as $friend) {
        $ref->addFriend($this->getReference($friend));
    }
}
0
Flosculus On

I haven't tested this, so it may be a bit buggy, however you should be able to see that I have held a reference to the reference key instead of the ref itself, then after creating the users, perform the friendship mapping after.

class Users extends AbstractFixture implements OrderedFixtureInterface {
    public function load(ObjectManager $manager) {
        $users = array(
            'user-1' => array(
                "password"        => "123456a",
                "email"           => "[email protected]",
                "friendsWithMe"   => array('user-2'),
                "myFriends"       => ""),
            'user-2' => array(
                "password"        => "123456b",
                "email"           => "[email protected]",
                "friendsWithMe"   => "",
                "myFriends"       => array('user-1'))
        );

        $friendships = array();

        foreach($users as $i => $user) {
            $i++;
            $document = new User();
            $document->setPassword($user["password"]);
            $document->setEmail($user["email"]);

            foreach ($user["myFriends"] as $friend) {
                $friendships[$i][] = $friend;
            }

            $manager->persist($document);
            $this->addReference($i, $document);

        }

        foreach($friendships as $k => $v) {
            $user = $this->getReference($k);
            $friend = $this->getReference($v);

            $user->addFriend($friend->addFriend($user));
        }

        $manager->flush();
    }

    public function getOrder() {
        return 0;
    }
}