In my CakePHP app I have models for Matches and Teams. Each Match has a home_team_id and an away_team_id, both of which reference a different Team.
In my team.php file, I am able to form the relationship for a Team's home matches:
var $hasMany = array(
'HomeMatch' => array('className' => 'Match', 'foreignKey' => 'home_team_id'),
'AwayMatch' => array('className' => 'Match', 'foreignKey' => 'away_team_id')
);
My problem is that I cannot automatically retrieve a Team's home and away Matches in a single array. That is, the retrieved Matches are returned in separate HomeMatch and AwayMatch arrays, which causes sorting difficulties.
I have tried the following:
var $hasMany = array(
'Match' => array('foreignKey' => array('home_team_id', 'away_team_id'))
);
...with no luck.
Any ideas on how to combine these two foreign keys into a single relationship?
Thanks, Ben
A custom finderQuery should do the trick:
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'finderQuery' => 'SELECT *
FROM `matches` as `Match`
WHERE `Match`.`home_team_id` = {$__cakeID__$}
OR `Match`.`away_team_id` = {$__cakeID__$}'
)
);
I was having a similar issue and instead of creating a finderQuery I used the conditions operator and it worked great!
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'conditions' => array(
'OR' => array(
array('Match.home_team_id' => '{$__cakeID__$}'),
array('Match.away_team_id' => '{$__cakeID__$}')
)
),
)
);
They are returned in seperate array's because the sort of represent different models (in this particular case the model is the same).
You should probably build a helper method to go over the retrieved data (in the model object or in a separate helper class) and "flatten" it. then you'd be able to sort it.
Ken.
Related
I have problem with HABTM models. When I try to fetch any related model f.e. like this:
$this->Tagi->find('first');
I dont get any results for associated model. Result looks like this:
array(
'Tagi' => array(
'id' => '1',
'nazwa' => 'sth'
),
'Instytucje' => array()
)
I am sure that there should be result, I've double checked it, even
$this->Tagi->getDataSource()->getLog(false, false)
shows correct query, that fetches right results.
If you have any idea whats wrong plz give me a hint.
Tagi model:
public $hasAndBelongsToMany = array(
'Instytucje' =>
array(
'className' => 'Instytucje.Instytucje',
'joinTable' => 'instytucje-tagi',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'instytucja_id',
'unique'=> true
)
);
Instytucje model:
public $hasAndBelongsToMany = array(
'Tagi' =>
array(
'className' => 'Instytucje.Tagi',
'joinTable' => 'instytucje-tagi',
'foreignKey' => 'instytucja_id',
'associationForeignKey' => 'tag_id',
'unique'=> true
)
);
EDIT:
Main problem is that HABTM refers to AppModel causing error:
Error: Table app_models for model AppModel was not found in datasource prod.
Which can be bypassed by adding $useTable in AppModel, which results in prior problem.
SOLVED
When using naming convention far beyond this Cake use, you have use third model with $useTable pointed on reference table.
Moreover its important to correctly point Cake to classes inside plugins.
I think you're problem relates to the fact that you are not using CakePHP's naming conventions. From the look of the query being generated Cake doesn't know how to correctly alias the join table, hence getting an AppModel alias in your query. I suspect that this is causing issues when Cake tries to build the results array from the query.
This might not work, but try creating a model for the joins table called InstytucjeTagi; then update your associations to use this using the with key:-
Tagi model:
public $hasAndBelongsToMany = array(
'Instytucje' =>
array(
'className' => 'Instytucje.Instytucje',
'joinTable' => 'instytucje-tagi',
'with' => 'InstytucjeTagi',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'instytucja_id',
'unique'=> true
)
);
Instytucje model:
public $hasAndBelongsToMany = array(
'Tagi' =>
array(
'className' => 'Instytucje.Tagi',
'joinTable' => 'instytucje-tagi',
'with' => 'InstytucjeTagi',
'foreignKey' => 'instytucja_id',
'associationForeignKey' => 'tag_id',
'unique'=> true
)
);
If this doesn't fix it try using the afterFind() callback in the Tagi model to check what Cake is returning:-
afterFind($results, $primary = false) {
parent::afterFind($results, $primary);
// Print out the $results to check what Cake is returning.
debug($results);
return $results;
}
I have a User model with two relations:
HasAndBelongsToMany
public $hasAndBelongsToMany = array(
'group' => array(
'className' => 'group',
'foreignKey' => 'user_id'
)
);
HasMany
public $hasMany = array(
'phonenumber' => array(
'className' => 'phonenumber',
'foreignKey' => 'user_id'
)
);
Phonenumber and Group have set
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
When I use
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
)
);
The data gets saved in the tabels but User_id is "0" in phonenumber and group table.
How can I get the correct ID saved ? (CakePHP v 2.5)
FWIW saveAll() should work as advertised, populating the new user_id in the child tables in one fell swoop.
Have you paid attention to the relevant options: atomic & deep?
Especially if database does not support transactions, you'll need to pass in atomic:
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
),
array('atomic' => false)
);
Considering the CakePHP documentation you will find this hint:
When working with associated models, it is important to realize that
saving model data should always be done by the corresponding CakePHP
model. If you are saving a new Post and its associated Comments, then
you would use both Post and Comment models during the save operation
(http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-hasone-hasmany-belongsto)
Based on that information I suggest you try the following:
$this->User->save($data); // first save the user
Assuming you have multiple numbers:
foreach($numbers as $key => $nbr) {
// set user_id related data
$numbers[ $key ]['Phonenumber']['user_id'] = $this->User->id;
}
Finally save your related data:
$this->User->Phonenumber->saveAll($numbers);
Since this code is untested you may need to take some adjustments. Always ensure to follow the Cake-Conventions and use CamelCase ModelNames.
I want to use aliases in cakephp(2.5.3) models which names are different from the name of the related model. The problem occures when I want o save data with saveAssociated method. My models have hasMany and belongsTo relations. When I want to save data it says there is na error. I dig the problem and think that this could be a problem with validateAssociated method which can change $data value or my knowledge of CakePHP is still on newbe level... Can anyone explain me what may I do wrong ?.
My Model A:
public $hasMany = array(
'alias1' => array(
'className' => 'AB',
'foreignKey' => 'a_id'
),
);
My Model AB:
public $belongsTo = array(
'alias2' => array(
'className' => 'A',
'foreignKey' => 'a_id'
),
'alias3' => array(
'className' => 'B',
'foreignKey' => 'b_id'
),
);
I try to save data with:
$this->alias2->alias1->saveAssocated($this->request->data)
Any idea ?
how to get name of (UserTransactionType.name) with Transaction.who_pay_fee_1,2,3 fields.
'user_transaction_type_id' works well but how to get the rest of fields work :(
//Transaction Model
public $belongsTo = array(
'UserTransactionType' => array(
'className' => 'UserTransactionType',
'foreignKey' => 'user_transaction_type_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
//UserTransactionType Model
public $hasMany = array(
'Transaction' => array(
'className' => 'Transaction',
'foreignKey' => 'user_transaction_type_id',
'dependent' => false,
))
This is the sample code for your controller:
$this->UserTransactionType->find('all',array(
'fields' => array('name'),
'contain' => array('Transaction')
)
);
If Models are associated you can specify in 'contain' which of them you want to get in the result.
If you want to have only some fields of related Model, you can determine them after 'Transaction' in 'contain' just like in the regular find() query:
'contain' => array('Transaction' => array('fields' => array('field_1',
'field_2') ))
But in your case, you don't need to specify fields, because by default you get all fields.
So no matter if you define or not fields "who_pay_fee_1,2,3" because if you use 'contain' by default you will get foreing_key - user_transaction_type_id.
I hope it's helpful
For people they like CakePhp :)
in Controller ->
get the list of 'UserTransactionType'
in View ->
after looping trough all the transactions; in Transaction Status column simply load the 'UserTransactionType'array and assign the number of array to $userTransactionTypes.
$userTransactionTypes[$transaction['Who_pay_fee_1']];
To be honest it was straight forward but needed a bit concentration :)
I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.