Friendships in CakePHP 2.x

2019-06-13 06:24发布

问题:

I'm having trouble setting up friendships with CakePHP 2.

I have two database tables: users and friends. My users table has the following columns:

  • id
  • email
  • password

And my friends table has the following columns:

  • id
  • user_id
  • friend_id
  • approved

I have friends set up as a hasAndBelongsToMany relationship in my Users model:

<?php
class User extends AppModel {
    public $hasAndBelongsToMany = array(
        'Friends' => array(
            'className' => 'User',
            'joinTable' => 'friends',
            'foreignKey' => 'user_id',
            'associationForeignKey' => 'friend_id',
            'unique' => true
        )
    );
}

Now, when I try and retrieve friends for a user, it only lists friendships that the specified user initiated, i.e. where user_id is equal to the user ID; it doesn't show me friends where the other person may have initiated the request (i.e. where the current user's ID is in the friend_id column).

How can I fetch friends, so records where either the user_id or friend_id column is equal to a particular ID?

回答1:

I don't think you understand how HABTM works. Read this part of the book. You will need a friends_users table in addition to the tables you have for the relationship to work. I think if you were going to set it up this way, you'd need to define a Friendship as having and belonging to many Users.

However, I question whether with your current setup you want a HABTM relationship. It seems like a user hasMany friends, and that's it. Look into using that relationship, and it'll give you the relevant ID as you expect it to. Don't forget to define Friend belongsTo User.


Here beings my canonical Cake 2.0 Friendship tutorial. I downloaded cakePHP 2.1 so I had a fresh start. I first changed my security salt and cipher, then added my database connection. Then I structured my database as follows:

Database:

users table:

id         | int(11)
created    | datetime
username   | varchar(255)

friendships table:

id         | int(11)
user_from  | varchar(255)
user_to    | varchar(255)
created    | datetime
status     | varchar(50)

Obviously, your users table can/will have more stuff, but this is the minimum I needed.

Models:

Okay this is the tricky part. Here are the relationship I defined in my User model.

class User extends AppModel {
/* Other code if you have it */
        var $hasMany = array(
          'FriendFrom'=>array(
             'className'=>'Friendship',
             'foreignKey'=>'user_from'
          ),
          'FriendTo'=>array(
             'className'=>'Friendship',
             'foreignKey'=>'user_to'
          )
       );
       var $hasAndBelongsToMany = array(
          'UserFriendship' => array(
              'className' => 'User',
              'joinTable' => 'friendships',
              'foreignKey' => 'user_from',
              'associationForeignKey' => 'user_to'
            )
       );
/* Again, other code */
}

Here is my Friendship model:

class Friendship extends AppModel {
/* Other code if you have it */
    var $belongsTo = array(
      'UserFrom'=>array(
         'className'=>'User',
         'foreignKey'=>'user_from'
      ),
      'UserTo'=>array(
         'className'=>'User',
         'foreignKey'=>'user_to'
      )
   );
/* Again, other code */
}

Note on models: The friendship model belongs to 2 users. The user model has 3 associations. The two hasMany relationships in the User Model are both aliases for the accessing the Friendship model's data, so we can use $this->User->FriendTo or $this->User->FriendFrom from controllers to get to the Friendship model. I at first called these UserFrom and UserTo, mirroring the setup of the Friendship model, but Cake threw a hissyfit about similarities, so I had to make them more distinct.

Controllers and Views: I baked controllers and views using the bake utility. I then created two users (Daniel and Martin) and created a new friendship from Daniel to Martin with a status of requested. I then updated the friendship status to confirmed.

I created the following viewless custom user action to demonstrate data retrieval about a friendship from the UsersController:

public function test() {
  $data = $this->User->FriendFrom->find('all', 
    array(
      'conditions'=>array('user_from'=>1), 
      'contain'=>array('UserTo')
    )
  );
  die(debug($data));
}

This find uses the hasMany relationship of the UserModel to access the Friendship model and get the related user_from and user_to data for the relationships where the user with the id of 1 initiated the relationships.

Your specific find:

Martin, the find you're looking for is super simple under this system, and while you could do it differently, you'd always be dealing with a similar method, simply as long as there are always two sides to a relationship. All you have to do is get a list of relationships where your user ID is either user1 or user2 (in my case, just so I know who initiated the relationship, I have them stored as user_to and user_from- I think this is what intimidated you). Then I iterate through the whole array, selecting the relevant friend data based on whether I am user1 or 2 in that given array. It's a really simple method, and I just put it in my user Model. Change the die(debug()); to return $friendslist to be able to call it from your controller and get an array back.

public function getFriends($idToFind) {
  $data = $this->FriendFrom->find('all', 
    array(
      'conditions'=>array(
        'OR'=> array(
            array('user_to'=> $idToFind),
            array('user_from'=> $idToFind)
        )
        )
    )
  );
  $friendslist = array();
  foreach ($data as $i) {
    if ($i['FriendFrom']['user_from'] == $idToFind){
        $friendslist[] = $i['UserTo'];
    }
    elseif ($i['FriendFrom']['user_to'] == $idToFind){
        $friendslist[] = $i['UserFrom'];
    }
  }

  die(debug($friendslist));
}