Search code examples
phpcakephpcakephp-model

How can I change the way cakephp passes the data for an associated model?


I've tried searching for an answer to this, as I'm sure its something that's been sorted out before, but I couldn't find anything discussing how cakephp formats the data returned from a Model. Normally I just see how the data comes, and handle it as needed for each Controller/View. In this case however, I want to reuse my views, unfortunately the way cakephp hands me the data from different controllers is causing me problems.

I have 2 models,associated as follows :

class Group extends AppModel {
public $hasMany = array(  'User' ); 
}

class User extends AppModel {
public $belongsTo = array(
'Group'  ); 
}

I want to have View/Groups/view/id show me details for the individual groups, as well as a list of users that belong to the group. Ideally, I don't want to recreate the code to display the user list, as it already exists in the View/Users/index

I found that I can use $this->extend('/Users/index') but I can't just use `$this->set('users',$group['User']), like I want, because the array is built differently.

In UsersController::index() I would call $users = $this->find('all'); which gives a list of users like this : debug($users);

array(
(int) 0 => array(
    'User' => array(
        'id' => '100',
        'group_id' => '101', 
    )
),
(int) 1 => array(
    'User' => array(
        'id' => '101',
        'group_id' => '101',
    )
)
)

In GroupsController is use $this->Group->contain(array('User')); $group = $this->GroupsController->Group->find('first','conditions'=>array('id'=>$id);

Which returns a list of Groups, but in this format instead debug($group['User']);

 'User' => array(
    (int) 0 => array(
        'id' => '101',
        'group_id' => '101', 
    ),
    (int) 1 => array(
        'id' => '100',
        'group_id' => '101',

    )
)

Is there anyway I can change how I call for my model data, to have it passed in a consistent manner on different Controllers, whether it is the main model or associated model? Or will I just have to cycle through the data each time and rebuild the data in the right format? This seems like once my dataset grows it will become an efficiency concern.


Solution

  • The reason why you're getting data in the odd format, is because you're using contain which is mean't to be getting related data (not direct-data).

    Instead of implementing normalization for this, I would recommend modifying your find() call:

    $group = $this->GroupsController->Group->User->find('list', array(
        'conditions' => array('User.group_id' => $id)
    ));
    

    Note: Using contain() when you don't need it adds additional JOINs to your SQL query, which will impact performance.

    I prefer not to normalize / reformat my CakePHP find-data, because it makes it non-standard. CakePHP always returns data in a predictable way, it's just a matter of getting used to it and using the appropriate methods to retrieve your data.

    Retrieving direct-data will return it in the format of:

      array(
        (int) 0 => array(
            'MODEL' => array(
                'field1' => 'value1',
                'field2' => 'value2', 
            )
        ),
        (int) 1 => array(
            'MODEL' => array(
                'field1' => 'value1',
                'field2' => 'value2',
            )
        )
      )
    

    Retrieving direct data AND related-data (by using Contain) will return it in the format of:

      array(
        (int) 0 => array(
            'MODEL' => array(
                'field1' => 'value1',
                'field2' => 'value2', 
                'RELATEDMODEL' => array(
                    (int) 0 => array(
                        'field1' => 'value1'
                        'field2' => 'value2'
                    ),
                    (int) 1 => array(
                        'field1' => 'value1'
                        'field2' => 'value2'
                    )
                )
            )
        ),
        (int) 1 => array(
            'MODEL' => array(
                'field1' => 'value1',
                'field2' => 'value2',
                'RELATEDMODEL' => array(
                    (int) 0 => array(
                        'field1' => 'value1'
                        'field2' => 'value2'
                    ),
                    (int) 1 => array(
                        'field1' => 'value1'
                        'field2' => 'value2'
                    )
                )
            )
        )
      )
    

    The reason why your data is different, is because you're telling MySQL to get the related data instead of getting it directly.

    If you really really want to normalize your data (which I wouldn't recommend for this), read on further:

    There are a few ways this could be done, but the most appropriate way would be to implement data normalization in your model's (or AppModel's) afterFind() callback.

    This callback is hit after every find on the applicable model. Implementing it in AppModel::afterFind() will affect every find call in your app.

    Side Note: An easy way to normalize data is to use Set::extract(), however, in my experience it can cause more overhead than looping through the data yourself because of how robust the implementation is. Might be a good idea to take a look and see if it fits your needs. Personally, I wouldn't use it myself in an afterFind() because of the over-head it could add, however, I'm including it here as it may fit your needs.

    1.3 Set Extract documentation:

    2.0 Set extract documentation:

    Regarding concerns on efficiency: Doing a regular loop won't add much overhead at all. With that said, however, normalizing will of course add a little bit of overhead.