CakePHP 2.4.2: Contain Not Working as Expected

2019-08-12 16:35发布

问题:

I have a model, Comment, that belongsto Module, Photo, Review, Article, and User. In turn, each of those models havemany Comment.

User belongsto Avatar and Avatar hasmany User

In the code below I successfully retrieve the latest 5 comments regardless of what model they come from (Photo, Article, Review) and I display some information about the User (username, online status).

$recentComments = $this->find('all',
array(
    'limit' => '5',
    'order' => array('Comment.id' => 'desc'),
    'conditions' => array(
        'Comment.page_id' => $page_id
    ),
    'contain' => array(
        'Photo' => array(
            'fields' => array('Photo.id'),
            'conditions' => array(
                'Comment.module_id' => 8
            ),
        ),
        'Review' => array(
            'fields' => array('Review.title', 'Review.id'),
            'conditions' => array(
                'Comment.module_id' => 2
            ),
        ),
        'Article' => array(
            'fields' => array('Article.title', 'Article.id'),
            'conditions' => array(
                'Comment.module_id' => 3
            ),
        ),
       'Module' => array(
            'fields' => array('Module.model', 'Module.post_title'),
        ),
      'User' => array(
            'fields' => array('User.username', 'User.online'),
        ),
    )
));

Unfortunately there are some issues with this. As you can see above I don't contain User->Avatar and all expected data is retrieved successfully.

$recentComments = Array
(
    [0] => Array
        (
            [Comment] => Array
                (
                    [id] => 179
                    [page_id] => 2
                    [module_id] => 3
                    [page] => 
                    [user_id] => 29
                    [post_id] => 9
                    [title] => test comment for article 9
                    [content] => here is the content for the comment
                    [created] => 2013-04-24 00:00:00
                    [redeemed] => 0
                    [status] => 0
                )

            [User] => Array
                (
                    [username] => bowlerae
                    [online] => 0
                )

            [Module] => Array
                (
                    [model] => Article
                    [post_title] => title
                )

            [Article] => Array
                (
                    [title] => Test title for article 9
                    [id] => 9
                    [likes] => 0
                    [dislikes] => 0
                    [comments] => 2
                )

            [Photo] => Array
                (
                    [id] => 
                )

            [Review] => Array
                (
                    [title] => 
                    [id] => 
                )

        )

However, if I DO contain User->Avatar like this...

   'User' => array(
        'fields' => array('User.username', 'User.online'),
        'Avatar' => array(
            'fields' => array('Avatar.file')
        )
    ),

Then the recursion basically becomes unlimited, ALL models related to Comment, User, Module, Article, Photo and Review are also retrieved which is A LOT.

Can anyone explain why this is happening? I will be happy to submit more code from some of the models if needed but I don't see any issues there.

Take a look at another example that works successfully...Below I am retrieving the 5 most recent articles. All information including the user Avatar is successfully retreived.

        $this->set('articles_sidebar', ClassRegistry::init('Article')->find('all',
array(
    'limit' => '5',
    'order' => array('Article.id' => 'desc'),
    'conditions' => array('
        Article.page_id' => $page_id
    ),
    'contain' => array(
       'User' => array(
            'fields' => array('User.username', 'User.online'),
            'Avatar' => array(
                'fields' => array('Avatar.file')
            )
        ),
    )
)));

Please note these two finds are performed in the AppController in the beforeRender given that $page_id > 0. $page_id is set in whatever the current controller is. I know people would probably ask about it but that's not what the issue is as I mentioned that example 2 retrieving the recent articles currently works.

EDIT: I discovered that it has something to do with my afterfind callback in the Article model. Is there a way I can tweak the queries in the afterfind and/or the $recentComments query so that they still work without breaking my contain? I don't need the likes, dislikes or comments virtual fields in my $recentComments query which is why they are not listed as one of the contained fields.

function afterFind($results, $primary = false) {
parent::afterFind($results, $primary);


          foreach($results as $key => $val){
               if (isset($val['Article']['id'])){    

                        $results[$key]['Article']['likes'] = $this->Like->find('count', array('conditions' => array('Like.post_id' => $results[$key]['Article']['id'], 'Like.module_id' => 3, 'Like.status' => 0)));


                        $results[$key]['Article']['dislikes'] = $this->Like->find('count', array('conditions' => array('Like.post_id' => $results[$key]['Article']['id'], 'Like.module_id' => 3, 'Like.status' => 1)));


                        $results[$key]['Article']['comments'] = $this->Comment->find('count', array('conditions' => array('Comment.post_id' => $results[$key]['Article']['id'], 'Comment.module_id' => 3, 'Comment.status < 2')));
               }
          } // end for each


      return $results;

} // end afterfind

回答1:

My guess is your problem is this:

When using ‘fields’ and ‘contain’ options - be careful to include all foreign keys that your query directly or indirectly requires.

Make sure you're including whatever field(s) are used to join User and Avatar models.

(read here)



回答2:

For now I changed (in the Article model afterFind)

 if (isset($val['Article']['id']))

to

 if (isset($val['Article']['id']) && $val == "article")

Seems to be working in current controller but need further testing. Is there a better solution?