简体   繁体   中英

Symfony2 Self referencing many to many relation

I'm new to symfony2 and forms are quite tough to get my mind around.

This topic has been covered before, but mainly to do with the relationship aspect. I'm having issues with the form and how to manage the saving of the relationships. The scenario being A user has many friends who are users. So a self referencing many-to-many relationship.

I'm using FOSUser Bundle and have a friendship entity. Here is the YAML for creating the entities.

MH\FriendshipBundle\Entity\Friendship:
type: entity
table: mh_friendship
repositoryClass: MH\FriendshipBundle\Entity\FriendshipRepository
id:
    id:
        type: integer
        generator:
            strategy: AUTO
fields:
    requested_at:
        type: datetime
        gedmo:
            timestampable:
                on: create
    is_accepted:
        type: boolean
        nullable: true
    accepted_at:
        type: datetime
        nullable: true
manyToOne:
    user:
        targetEntity: MH\UserBundle\Entity\User
        inversedBy: user_friends
        joinColumn:
            name: user_id
            referencedColumnName: id
    friend:
        targetEntity: MH\UserBundle\Entity\User
        inversedBy: friend_users
        joinColumn:
            name: friend_id
            referencedColumnName: id
lifecycleCallbacks:
    prePersist:   [ ]
    postPersist:  [ ]
    preUpdate:    [ ]
    postUpdate:   [ ]


MH\UserBundle\Entity\User:
type:  entity
table: mh_user
repositoryClass: MH\UserBundle\Entity\UserRepository
id:
    id:
        type: integer
        generator:
            strategy: AUTO
fields:
    first_name:
        type: string
        length: 100
        nullable: true
    last_name:
        type: string
        length: 100
        nullable: true
    created_at:
        type: datetime
        gedmo:
            timestampable:
                on: create
    updated_at:
        type: datetime
        gedmo:
            timestampable:
                on: update
oneToMany:
    user_friends:
        targetEntity: MH\FriendshipBundle\Entity\Friendship
        mappedBy: user
        cascade: ["persist", "remove"]
    friend_users:
        targetEntity: MH\FriendshipBundle\Entity\Friendship
        mappedBy: friend
        cascade: ["persist", "remove"]
    friend_groups:
        targetEntity: MH\FriendshipBundle\Entity\FriendGroup
        mappedBy: owner
        cascade: ["persist", "remove"]
lifecycleCallbacks:
    prePersist:   [ ]
    postPersist:  [ ]
    preUpdate:    [ ]
    postUpdate:   [ ]

Now i have a form that was create via the crud generator from the Friendship resource, and this is what i'm doing.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('friend', 'entity', array(
                            'class' => 'UserBundle:User',
                            'property' => 'fullName',
                            'expanded' => true,
                            'multiple' => true,
                            'required' => false,
                    ))
    ;

}

Which renders a list of checkboxes of users that i can select and save as "friends".

My problems are :

  1. I get the following error when saving.

PHP Catchable fatal error: Argument 1 passed to MH\\FriendshipBundle\\Entity\\Friendship::setFriend() must be an instance of MH\\UserBundle\\Entity\\User, instance of Doctrine\\Common\\Collections\\ArrayCollection given, called in /Users/mohammedhamad/Sites/sociabills/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 345 and defined in /Users/mohammedhamad/Sites/sociabills/src/MH/FriendshipBundle/Entity/Friendship.php on line 155, referer: http://sociabills.local/friends/new

Not sure how to make sure that the chosen are "friends" are passed to the setFriends method which expects a collection, and not the setUser method which expects a User object.

  1. How to i get the form to list all other users, except for the logged in user. Don't want a person to befriend themselves.

Looking at the doc

/**
 * @ManyToMany(targetEntity="User", mappedBy="myFriends")
 **/
private $friendsWithMe;

/**
 * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
 * @JoinTable(name="friends",
 *      joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
 *      inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
 *      )
 **/
private $myFriends;

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

// ...

}

The YML equivalent of the annotations would be (keep in mind I'm not used to yml for entity mapping and this is not tested)

   manyToMany:
     myFriends:
       targetEntity: User
       inversedBy: friendsWithMe
       joinTable:
         name: friends
         joinColumns:
           user_id:
             referencedColumnName: id
         inverseJoinColumns:
           friends_user_id:
             referencedColumnName: id
     friendsWithMe:
       targetEntity: User
       mappedBy: myFriends

Then you can implement addMyFriend($friend) method and removeMyFriend($friend) in your class

public function addMyFriend($friend){

 $this->myFriends[] = $friend;

}

public function removeMyFriend($friend)
{
    $this->myFriends->removeElement($friend);
}

Issue 2 was answered by @Darragh

Issue 1:

As I'm sure you noticed from the error, the first issue is occurring because setFriend() expects a single User entity, but is instead receiving an ArrayCollection of one or more User entities.

This was discussed here in a blog post by Bernhard Schussek, the creator of the form component. I assume that you are using version >= 2.1? If so, the you need to add some additional methods:

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

and:

public function removeFriend(User $friend)
{
    $this->friends->removeElement($friend);
}

My understanding is that these methods are called for every element in the ArrayCollection . I am looking at a similar example in one of my repos and this is what is implemented in one of my entities.

This is available in version >= 2.1.

Issue 2:

To populate a form entity element with custom choices, you can use the query_builder option. A quick cut and paste example from my codebase here:

->add('client', 'entity', array(
    'class'         => 'HooluxDAPPUserBundle:Organisation',
    'property'      => 'name',
    'query_builder' => function(EntityRepository $er) use ($builder) {
        if ($builder->getData()->hasAdvertiser()) {
            return $er->buildQueryClientsForAdvertiser(
                $builder->getData()->getAdvertiser()
            );
        }
        return $er->createQueryBuilder('c');
    }
))

The query_builder option accepts an anon function/closure that receives a reference to the repository class for your Entity. You can call any custom method that returns a query builder object.

Edit

In your controller:

// pass current user's own id as an option 
$this->createForm(new FriendType(), $entity, array('user_id' => $this->getUser()));

In the buildForm method:

public function buildForm($builder, $options) {
    $builder
        ->add('friend', 'entity', array(
            'class' => 'UserBundle:User',
            'property' => 'fullName',
            'expanded' => true,
            'multiple' => true,
            'required' => false,
            // closure, include $options from parent scope with `use`
            'query_builder' => function(EntityRepository $er) use ($options) {
                // current user's own id
                $userId = $options['user_id'];
                // call custom method on UserRepository class
                return $er->getUsersWhereIdIsNot($userId); // returns QueryBuilder object

            }
        ));
}

Then add a method that returns all users WHERE id != :id or whatever.

Hope this helps :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM