Cake PHP joining same model on two tables - cakephp

I have a question regarding model association. Consider the following application: each month a new question is asked to the users. These have the ability to give a tip on this question and other users have the ability to react on this tip. My model-setup is like this:
Users hasMany Tips, Reactions
Tips belongsTo User
Tips hasMany Reactions
Reactions belongsTo User, Tip
Now I want to get all the tips on that question from all users. I do a simple find all on the tips-table which return me all tips joined with their user and reactions. The reactions however do not contain the user, which is very important, obviously.
Cake automatically converts my find all to two queries: one on the tips-table joining the user and one on the reactions table, but there is no join on the users-table, which is what i need.
So my question is: why is the users-table not joined with the reactions and how can i solve this?
Code:
User-model:
public $hasMany = array('Tip', 'Reaction');
Tip-model:
public $hasMany = array('Reaction' => array(
'foreignKey' => 'tip_id'
));
public $hasOne = array('User' => array(
'fields' => array(
'User.id, User.first_name, User.last_name'
),
'foreignKey' => 'id'
));
Reaction-model:
public $belongsTo = array('Tip', 'ReactionUser' => array(
'className' => 'User',
'foreignKey' => 'id'
));
Controller:
$tips = $this->Tip->find('all');
OUTPUT:
array(
(int) 0 => array(
'Tip' => array(
'id' => '1',
'user_id' => '1',
'question_id' => '1',
'tip' => 'Testing da new datavbase',
'votes' => '0',
'approved' => '1',
'pic' => '',
'created' => '2012-05-18 09:22:56'
),
'User' => array(
'id' => '1',
'first_name' => 'Bart',
'last_name' => 'De Bock'
),
'Reaction' => array(
(int) 0 => array(
'id' => '1',
'tip_id' => '1',
'user_id' => '1',
'reaction' => 'lorem ipsum',
'votes' => '0',
'approved' => '1',
'pic' => '',
'created' => '0000-00-00 00:00:00'
)
)
),
(int) 1 => array(
'Tip' => array(
'id' => '2',
'user_id' => '2',
'question_id' => '1',
'tip' => 'Test',
'votes' => '0',
'approved' => '1',
'pic' => '',
'created' => '2012-05-18 10:22:51'
),
'User' => array(
'id' => '2',
'first_name' => 'Deborah',
'last_name' => 'Rochtus'
),
'Reaction' => array(
(int) 0 => array(
'id' => '2',
'tip_id' => '2',
'user_id' => '3',
'reaction' => 'Say whaat?',
'votes' => '2',
'approved' => '1',
'pic' => '',
'created' => '2012-05-18 10:33:32'
),
(int) 1 => array(
'id' => '3',
'tip_id' => '2',
'user_id' => '4',
'reaction' => 'Hmmm..',
'votes' => '0',
'approved' => '1',
'pic' => '',
'created' => '2012-05-18 10:33:44'
)
)
)
)
As you can see, these reactions do not contain the associated user...
Thanks for your help!

Use Containable:
In AppModel:
public $uses = array('Containable');
Change your find call to:
$this->Tip->find('all', array('contain' => array('User', 'Reaction' => array('User'))));

Please try this
$tips = $this->Tip->find('all'
array(
'recursive'=>'2'
)
);
recursive value maybe 1, 2, 3, 4 etc as you need.

Related

CakePHP joing two tables

I have a problem in viewing my data ,I need to return a year,ex(2010,2011,2012,2013) instead of returning the year id , I used contain but It did not work , what is the right way to displaying data ?!
Here is the output array:array(
(int) 0 => array(
'IndicatorDatum' => array(
'id' => '188',
'indicator_id' => '2',
'report_year_id' => '2',
'value' => '144063181',
'comment' => '0',
'reference' => '0'
)
),
(int) 1 => array(
'IndicatorDatum' => array(
'id' => '163',
'indicator_id' => '2',
'report_year_id' => '3',
'value' => '178104575',
'comment' => '0',
'reference' => '0'
)
),
(int) 2 => array(
'IndicatorDatum' => array(
'id' => '137',
'indicator_id' => '2',
'report_year_id' => '4',
'value' => '198637602',
'comment' => '0',
'reference' => '0'
)
),
(int) 3 => array(
'IndicatorDatum' => array(
'id' => '5752',
'indicator_id' => '2',
'report_year_id' => '5',
'value' => '1234',
'comment' => null,
'reference' => null
)
),
(int) 4 => array(
'IndicatorDatum' => array(
'id' => '5694',
'indicator_id' => '2',
'report_year_id' => '6',
'value' => '100',
'comment' => '0',
'reference' => '0'
)
),
(int) 5 => array(
'IndicatorDatum' => array(
'id' => '5271',
'indicator_id' => '2',
'report_year_id' => '6',
'value' => '1',
'comment' => '0',
'reference' => '0'
)
)
)
Model
public $belongsTo = array(
'Indicator' => array(
'className' => 'Indicator',
'foreignKey' => 'indicator_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'ReportYear' => array(
'className' => 'ReportYear',
'foreignKey' => 'report_year_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Organisation' => array(
'className' => 'Organisation',
'foreignKey' => 'organisation_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
$this->find('all',array(
'fields' => array('IndicatorDatum.id','IndicatorDatum.indicator_id','IndicatorDatum.report_year_id','IndicatorDatum.value','IndicatorDatum.comment','IndicatorDatum.reference'
),'contain' => array(
'ReportYears'),
'conditions'=>array('IndicatorDatum.indicator_id' =>'2' , 'IndicatorDatum.organisation_id'=>$organisation_id),
'order' => array('IndicatorDatum.report_year_id')
));
You just need to specify the exact fields for each model(including associated models), otherwise only the id is being returned (and it is good, because if it would be all the fields, what if you have some text column in the db with KBs of data - you do not want to retrieve it)
also, please pay attention, that by default(and by cake conventions) model names are singular, so, inside contain it should be ReportYear and not ReportYears.
here is the final query.
$this->find('all', array(
'fields' => array(
'IndicatorDatum.id',
'IndicatorDatum.indicator_id',
'IndicatorDatum.report_year_id',
'IndicatorDatum.value',
'IndicatorDatum.comment',
'IndicatorDatum.reference'
),
'contain' => array(
'ReportYear' => array(
'fields' => array(
'ReportYear.id',
'ReportYear.year' // not sure what you have as a year field name
)
)
),
'conditions' => array(
'IndicatorDatum.indicator_id' => '2',
'IndicatorDatum.organisation_id' => $organisation_id
),
'order' => array(
'IndicatorDatum.report_year_id'
)
)
);

CakePHP find remove fields or recursive find

I'm trying to slim down my finds queries, by specifying which fields to use. I don't understand how I can specify specific fields for my HABTM fields.
This is my find:
$lastviewed = $this->Lastviewedproduct->find('all', array(
'fields' => array(
'Lastviewedproduct.id', 'Lastviewedproduct.sessionid', 'Lastviewedproduct.product_id',
'Product.id', 'Product.name', 'Product.slug', 'Product.price',
//'Category.id',
//'Mediafile.*',
),
'conditions' => array(
'Lastviewedproduct.status' => 'active',
'Lastviewedproduct.sessionid' => $this->Session->read('Companyname.sessionid'),
'Product.hidden_on_site' => 0,
'Product.visibility' => 1,
'Product.status' => 'active',
),
'order' => 'Lastviewedproduct.modified DESC',
'limit' => 5,
'recursive' => 2,
));
This is what it returns:
(int) 2 => array(
'Lastviewedproduct' => array(
'id' => '97',
'sessionid' => '14035312318244955',
'product_id' => '2'
),
'Product' => array(
'id' => '2',
'name' => 'blokhut 2',
'slug' => 'blokhut-2',
'price' => '200.00',
'Category' => array(
(int) 0 => array(
'id' => '218',
'zosomodule_id' => '25',
'user_add_id' => '0',
'user_update_id' => '0',
'created' => '2013-11-27 10:00:00',
'modified' => '2013-11-27 10:00:00',
'status' => 'active',
'visibility' => true,
'parent_id' => '2',
'shipment_id' => '1',
'name' => 'Metalen Tuinbergingen ',
'slug' => 'metalen-tuinbergingen-',
'pagetitle' => 'Metalen Tuinbergingen ',
'content' => '<p class="p1"><span class="s1">Metalen tuinbergingen zijn een praktische en kwalitatieve oplossing voor meer bergruimte in uw tuin. Wij hebben diverse merken metalen bergingen zoals </span><span class="s2">Biohort</span><span class="s1"> en </span><span class="s2">Yardmaster</span><span class="s1">. Hier hebben wij tevens ook tuinkasten en opbergboxen van.</span></p>
<p class="p1"><span class="s1"><br /></span></p>
<p class="p1"><span class="s1">Mocht u op zoek zijn naar andere varianten tuinbergingen, bekijk dan ons gehele assortiment </span><span class="s2">tuinbergingen.</span></p>',
'metatitle' => 'Metalen tuinbergingen',
'metadescription' => null,
'discountoffer' => false,
'discount_normal' => '0.00',
'discount_max' => '0.00',
'discount_ideal' => '0.00',
'cost_rembours' => '0.00',
'in_mainmenu' => true,
'hide_sidebar' => false,
'show_block' => true,
'sort_order' => '0',
'ProductsCategory' => array(
'id' => '2',
'created' => '2014-01-01 10:00:00',
'modified' => '2014-01-01 10:00:00',
'product_id' => '2',
'category_id' => '218'
)
)
),
'Mediafile' => array()
)
),
I don't want all these Category fields, but only Category.id, Category.name and Category.slug for example. How do i say that in my find? As you can see I tried adding Category.id in my fields array, but then I get an error. I read something about containable, but I didn't get that working and I don't know if that is the solution.
Thanks in advance for your help!
You can use ContainableBehavior for this task. You should write something like this:
1 attach behavior
permanent
class Lastviewedproduct extends AppModel {
public $actsAs = array('Containable');
....
}
on demand
$this->Lastviewedproduct->Behaviors->load('Containable');
2 specify fields
$this->Lastviewedproduct->find('all', array(
'contain' => array(
'Product' => array(
'Category' => array(
'fields' => array('id','name','slug')
),
),
'Mediafile'
)
));
Add field parameter with relation ship where you give relationship to Category MODEL
public $hasMany = array(
'Category' => array(
'className' => 'Category',
'fields' => array('Category.id','Category.name','Category.slug')
)
);
As Dimitry said above - Containable is the way to go with this:
In your Models/AppModel.php (Create it if it doesn't exist) change the call to have the following:
class AppModel extends Model {
public $actsAs = array('Containable');
}
Then you would do as Dimitry said above - this will allow you to only return the fields you want.
'recursive' => 2
will eventually cause you so much slowness that your app will be painful to use, most people set recursive = 1 in their AppModel and use containable the rest of the time.

Cake PHP HABTM relationship with same model

I have a products table and a products_relateds table. This holds 2 references to products (product_id and related_id)
I'm trying to set up a HABTM relationship for this like so in my Product Model
public $hasAndBelongsToMany = array(
'Related' => array(
'className' => 'Product',
'joinTable' => 'products_relateds',
'foreignKey' => 'product_id',
'associationForeignKey' => 'related_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
Does this look right? Do i need to create a Related model?
I get no errors, and viewing the products page will not load when this relationship is in place
That code is fine, your error must be elsewhere. Using $this->Product->findBy(1) should return something like this:
array(
'Product' => array(
'id' => '1',
'name' => 'Test 1'
),
'Related' => array(
0 => array(
'id' => '2',
'name' => 'Test 2',
'ProductsRelated' => array(
'id' => '3',
'product_id' => '1',
'related_id' => '2'
)
),
1 => array(
'id' => '3',
'name' => 'Test 3',
'ProductsRelated' => array(
'id' => '1',
'product_id' => '1',
'related_id' => '3'
)
)
)
)

cakePHP 2.0 Associated models data not being retrieved

I have 2 tables, Militia and Injunctions. Militia can have many injunctions and injunctions only belong to one militia. I set those up in the models but when i call a find all for militia the injunctions aren't pulled out.
Militia model
class Militia extends AppModel {
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
public $hasMany = array(
'Injunction' => array(
'className' => 'Injunction',
'foreignKey' => 'militia_id'
//'conditions' => array("not" => array('Injunction.removed' => null))
)
);
}
Injunctions model
class Injunction extends AppModel {
public $belongsTo = array(
'Militia' => array(
'className' => 'Militia',
'foreignKey' => 'militia_id'
)
);
}
and the query getting the militia members
$user = $this->Session->read("User");
$militias = $this->Militia->find('all',
array(
'conditions'=>array(
"Militia.user_id" => $user['User']['id'],
"Militia.deleted" => 0
)
)
);
output
/app/Controller/UsersController.php (line 41)
array(
(int) 0 => array(
'Militia' => array(
'id' => '26',
'first_name' => 'Chris',
'last_name' => 'Morris',
'created' => '2013-02-11 13:45:24',
'user_id' => '2',
'status' => '1',
'deleted' => '0'
)
),
(int) 1 => array(
'Militia' => array(
'id' => '31',
'first_name' => 'John',
'last_name' => 'Smith',
'created' => '2013-02-11 14:03:50',
'user_id' => '2',
'status' => '0',
'deleted' => '0'
)
),
(int) 2 => array(
'Militia' => array(
'id' => '32',
'first_name' => 'test',
'last_name' => 'user',
'created' => '2013-02-11 14:21:38',
'user_id' => '2',
'status' => '0',
'deleted' => '0'
)
),
(int) 3 => array(
'Militia' => array(
'id' => '33',
'first_name' => 'test',
'last_name' => 'user',
'created' => '2013-02-11 14:24:02',
'user_id' => '2',
'status' => '1',
'deleted' => '0'
)
)
)
I've done the same thing before on other projects but for some reason this one time it's not pulling out the associated data. It's probably something stupid like a typo but I've been looking and testing and can't find anything wrong.
Check ORM (hasOne, hasMany, belongsTo, hasAndBelongsToMany) definition,
Check if foreign key ids are set right
Check if there are data's in the table and its related table.
Check the results of find without condition
Check to see if recursive level is greater than 0
To rule out your old cached query issue, set debug level higher to 0
Look out in the bottom section of sql dump to see what query is being fired, if it feels ok try same query with SQL away from Cake/Php.

CakePHP 2.1 Avg function for query

I am using cakephp 2.1 and I wanna get average of movie ratings for perticular movie.
Where 'Movie' is a model and it contains 'Language' and 'CriticReview' as models.
The array structure is as follows.
array(
(int) 0 => array(
'Movie' => array(
'id' => '4',
'name' => 'Maattrraan',
'cover_image' => '/movies/4d0d1c41d956a2cb2ab93603d1fdf70c.jpg',
'release' => '2012-10-12',
'runtime' => '',
'budget' => '65 crore',
'box_office' => '',
'language_id' => '2',
'industry_id' => '3'
),
'Language' => array(
'id' => '2',
'name' => 'tamil'
),
'CriticReview' => array(
(int) 0 => array(
'rating' => '4',
'movie_id' => '4'
),
(int) 1 => array(
'rating' => '3',
'movie_id' => '4'
)
)
),
(int) 1 => array(
'Movie' => array(
'id' => '1',
'name' => 'Romeo',
'cover_image' => '/movies/32aa2788fa5e1584d4c627c56214574e.jpg',
'release' => '2012-07-06',
'runtime' => '',
'budget' => '',
'box_office' => '',
'language_id' => '1',
'industry_id' => '1'
),
'Language' => array(
'id' => '1',
'name' => 'kannada'
),
'CriticReview' => array(
(int) 0 => array(
'rating' => '6',
'movie_id' => '1'
),
(int) 1 => array(
'rating' => '3',
'movie_id' => '1'
)
)
));
So I have to avg of critic review. Please help me to find out the solution. The work will be appreciable.
I don't know how your query looks like now, but you can try to add this field:
$this->Movie->find("all", array(
"fields" => array("AVG(CriticReview.rating) AS AverageRating"),
"conditions" => ...
));
The array returned will now contain a sub array within each movie with a key 0 like this
[0] => Array
(
[AverageRating] => AVG_CALCULATED_BY_MYSQL
)
I personally prefer to save calculated data instead of calculate it on fly. Maybe you'd like to read this question: MySQL - Calculating fields on the fly vs storing calculated data

Resources