4

I'm having trouble composing a CakePHP find() which returns the records I'm looking for.

My associations go like this:

User ->(has many)-> Friends ,
User ->(has many)-> Posts

I'm trying to display a list of all a user's friends recent posts, in other words, list every post that was created by a friend of the current user logged in.

The only way I can think of doing this is by putting all the user's friends' user_ids in a big array, and then looping through each one, so that the find() call would look something like:

$posts = $this->Post->find('all',array(
            'conditions' => array(
                'Post.user_id' => array(
                    'OR' => array(
                        $user_id_array[0],$user_id_array[1],$user_id_array[2] # .. etc
                    )
                )           
            )
        ));

I get the impression this isn't the best way of doing things as if that user is popular that's a lot of OR conditions. Can anyone suggest a better alternative?

To clarify, here is a simplified version of my database:

"Users" table
id
username
etc

"Friends" table
id
user_id
friend_id
etc

"Posts" table
id
user_id
etc

harpax
  • 5,986
  • 5
  • 35
  • 49
gomezuk
  • 299
  • 6
  • 15
  • What do you mean by "better"? What do you want to improve? – sibidiba Mar 08 '10 at 15:27
  • Well, in the above example, if the user had 100 friends, that would mean a very long SQL query. I thought there might be a more efficient way of constructing the query. – gomezuk Mar 08 '10 at 15:40
  • how do you know who is friends with who? – Leslie Mar 08 '10 at 16:13
  • @Leslie good question. There is a friend_id in the "Friends" table which refers to the other user in the friendship. – gomezuk Mar 08 '10 at 16:18
  • You should read the answer from cdburgess again, as i would think that that is the solution you are looking for. – harpax Mar 09 '10 at 08:10

5 Answers5

2

After reviewing what you have rewritten, I think I understand what you are doing. Your current structure will not work. There is no reference in POSTS to friends. So based on the schema you have posted, friends CANNOT add any POSTS. I think what you are trying to do is reference a friend as one of the other users. Meaning, A users FRIEND is actually just another USER in the USERS table. This is a self referential HABTM relationship. So here is what I would propose:

1- First, make sure you have the HABTM table created in the DB:

-- MySQL CREATE TABLE users_users ( user_id char(36) NOT NULL,
friend_id char(36) NOT NULL );

2- Establish the relationships in the User model.

var $hasAndBelongsToMany = array(
'friend' => array('className' => 'User',
  'joinTable' => 'users_users',
  'foreignKey' => 'user_id',
  'associationForeignKey' => 'friend_id',
  'unique' => true,
  ),
);

var $hasMany = array(
'Post' => array(
  'className' => 'Post',
  'foreignKey' => 'user_id'
),
);

3- use the scaffolding to insert a few records, linking friends and adding posts. 4- Add the view record function to the Users controller:

function get_user($id)
{
  $posts = $this->User->find('first', array(
      'conditions' => array('User.id' => $id),
      'recursive' => '2'
  ));
  pr($posts);
}

5- Now you can query the User table using recursive to pull the records using the following command:

http://test/users/get_user/USER_ID

6- Your output will show all of the records (recursively) including the friends and their posts in the returned data tree when you pr($posts)

I know this is a long post, but I think it will provide the best solution for what you are trying to do. The power of CakePHP is incredible. It's the learning curve that kills us.

Happy Coding!

Chuck Burgess
  • 11,600
  • 5
  • 41
  • 74
  • Sorry, there was a mistake in my original post. The Friend and Post models aren't actually related. There is no "friend_id" field in the Posts table, just a "user_id". – gomezuk Mar 08 '10 at 15:52
  • Ok! I think I got it! If you have any questions about how I have set it up, just let me know. – Chuck Burgess Mar 09 '10 at 04:18
  • Brilliant! You managed to get to the bottom of my problem without me really realising what my problem was. Thank you very much!! – gomezuk Mar 09 '10 at 13:56
  • Glad I could help. Once you rewrote it and I saw it, I knew instantly. (Because I have been there before.) The thing I love about CakePHP is the minimalistic approach provided by the framework. This will be much more efficient than writing code to do something cake can already do. Happy Coding! – Chuck Burgess Mar 09 '10 at 14:55
0

If Post.user_id points to Friend.id (which wouldn't follow the convention btw) then it would be

$posts = $this->Post->find('all',array(
    'conditions' => array(
        'Post.user_id' => $user_id_array      
    )
);

which would result in .. WHERE Post.user_id IN (1, 2, 3) ..

harpax
  • 5,986
  • 5
  • 35
  • 49
  • Sorry I've realised my original post was wrong. There is no association between Friends and Posts, which is why the above won't work. The associations go: User ->(has many)-> Friends , User ->(has many)-> Posts – gomezuk Mar 08 '10 at 15:33
0

Depending on your setup, it might be quicker to run two queries rather than trying to chain them together via the Cake stuff. I'd recommend adding something like getFriendsPosts() in the Users model.

<?php
 class UserModel extends AppModel {
 // ... stuff
  function getFriendsPosts( $user_id )
  {
   $friends = $this->find( ... parameters to get user IDs of all friends );
   // flatten the array or tweak your params so they fit the conditions parameter.  Check out the Set class in CakePHP
   $posts = $this->find( 'all', array( 'conditions' => array( 'User.id' => $friends ) ) );
   return $posts;
  }
 }
?>

Then to call it, in the controller just do

$friends = $this->User->getFriendsPosts( $this->Auth->User('id') );

HTH, Travis

Travis Leleu
  • 4,190
  • 2
  • 27
  • 33
0

Isn't CakePHP already generating the efficient code of:

SELECT * from Posts WHERE user_id IN (id1, id2 ...)

if not, you can do

$conditions='NULL';
foreach($user_id_array as $id) $conditions.=", $id";

$posts = $this->Posts->find('all', array(
    'conditions' => "Post.user_id IN ($conditions)",
));
sibidiba
  • 6,270
  • 7
  • 40
  • 50
0

If your models are properly associated, Cake will automatically retrieve related model records. So, when you search for a specific user, Cake will automatically retrieve related friends, and related posts of these friends. All you need to do is set the recursion level high enough.

$user = $this->User->find('first', array('conditions' => array('User.id' => $id), 'recursive' => 2));
debug($user);

// gives something like:
array(
    User => array()
    Friend => array(
        0 => array(
            ...
            Post => array()
        ),
        1 => array(
            ...
            Post => array()
        )
    )
)

All you need to do is extract the posts from the user's friends, which is as easy as:

$postsOfFriends = Set::extract('/Friend/Post/.', $user);
deceze
  • 510,633
  • 85
  • 743
  • 889