How do I traverse CakePHP relations? - php

I'm trying to traverse the relations on CakePHP models.
This is the database:
I can access product attributes (product->attributes) on a product model but I cannot access the product attributes on Ad model (ad->product->attributes).
Here is my code:
//Product Model
class Product extends AppModel {
public $useTable = 'products';
public $displayField = 'name';
public $hasAndBelongsToMany = array(
'Attributes' =>
array(
'className' => 'Attribute',
'joinTable' => 'product_has_attributes',
'foreignKey' => 'products_id',
'associationForeignKey' => 'attributes_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'product_has_attributes'
)
);
public $hasMany = array(
'Ads' => array(
'className' => 'Ad',
'foreignKey' => 'Products_id',
'conditions' => '',
'order' => '',
'limit' => '',
'dependent' => true
)
);
//Ad Model
class Ad extends AppModel {
public $displayField = 'Name';
public $belongsTo = array(
'Product' => array(
'className' => 'Products',
'foreignKey' => 'products_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
//Attribute Model
class Attribute extends AppModel {
public $displayField = 'name';
public $hasAndBelongsToMany = array(
'Products' =>
array(
'className' => 'Product',
'joinTable' => 'product_has_attributes',
'foreignKey' => 'attributes_id',
'associationForeignKey' => 'products_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'product_has_attributes'
)
);
// Products controller -> Action View
class ProductsController extends AppController {
public function view($id = null) {
if (!$this->Product->exists($id)) {
throw new NotFoundException(__('Invalid product'));
}
$options = array('conditions' => array('Product.' . $this->Product->primaryKey => $id));
$this->set('product', $this->Product->find('first', $options));
}
}
// Ads controller -> Action View
class AdsController extends AppController {
public function view($id = null) {
if (!$this->Ad->exists($id)) {
throw new NotFoundException(__('Invalid ad'));
}
$options = array('conditions' => array('Ad.' . $this->Ad->primaryKey => $id));
$this->set('ad', $this->Ad->find('first', $options));
}
And here is what I do in the views:
//Products Views: snipet of view.ctp
print_r ($product);
// this prints rhe product and all associated attributes
//Ads Views: snipet of view.ctp
print_r ($ad['Product']);
//this will print only the product fields, but not the attributes associated to the product
What is wrong? How do I access the relation Ad->product->attribute from my Ad model?

I can think of a couple of simple ways to accomplish this.
First:
class AdsController extends AppController {
if (!$this->Ad->exists($id)) {
throw new NotFoundException(__('Invalid ad'));
}
$options = array(
'conditions' => array('Ad.' . $this->Ad->primaryKey => $id),
'recursive' => 1, // or more
);
$this->set('ad', $this->Ad->find('first', $options));
}
That would be the simplest code change to make sure you get attributes that are related to the product that is returned.
Otherwise, you alluded to this in your question. You can access related Models through the model, so:
$this->Ad->Product->find(...);
I have had issues with Cake not liking my find conditions when using related models in conditions, and I'm not sure of the proper syntax, but if you wanted to pursue that, I'm sure you can track it down through the docs or by experimentation.
Finally, I would advise you to check into the ContainableBehavior, which will allow you to fine tune which fields are actually returned in the results. I hope this answers your question!

Perhaps the problem could be that you're not using CakePHP's conventions.
From the docs:
"new join table’s name needs to include the names of both models involved, in alphabetical order, and separated with an underscore ( _ )."
So, your join table should be named attributes_products.
Also, check your foreign keys. They should be in singular form.
attributes_products.id
attributes_products.product_id
attributes_products.attribute_id
Hopefully that solves the problem.
References:
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
http://book.cakephp.org/2.0/en/getting-started/cakephp-conventions.html#model-and-database-conventions

Related

Changing the order items are retrieved from the DB in CakePHP?

I've made a forum using CakePHP while following a tutorial and would like to expand it.
Currently, in a post it allows me to comment, but they are displayed as newest -> oldest, so it doesn't make as much sense and I would like to reverse this.
This is the current association I have, I'm pretty new to PHP/Cake so I'm not sure where to go from here, any help is very appreciated!
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'forum_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
For those asking this is the function in my controller:
public function add($topicId=null) {
if ($this->request->is('post')) {
$this->request->data['Post']['user_id'] = $this->Auth->user('id');
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__('Post has been created'));
$this->redirect(array('controller'=>'topics','action'=>'view',$this->request->data['Post']['topic_id']));
}
} else {
if (!$this->Post->Topic->exists($topicId)) {
throw new NotFoundException(__('Invalid topic'));
}
$this->Post->Topic->recursive = -1;
$topic = $this->Post->Topic->read(null,$topicId);
$this->Post->Forum->recursive = -1;
$forum = $this->Post->Forum->read(null,$topic['Topic']['forum_id']);
$this->set('topic',$topic);
$this->set('forum',$forum);
}
}
Something like this should do it:
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'forum_id',
'dependent' => false,
'order' => 'Post.created ASC' //order the Posts in ascending order based on whe they were created
)
Assuming you have a created column in your database table.
Edit:
You can find more info in the CakePHP documentation http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Avoid using read(), it is better to use find('first'). So you could rewrite your queries like:-
$this->Post->Topic->find('first', array(
'contain' => array(
'Post' => array(
'order' => array(
'Post.created' => 'asc',
'Post.id' => 'asc'
)
)
)
));
Passing an array instead of a string for order allows you to sort by multiple conditions. The above example would create the following ORDER SQL:-
ORDER BY Post.created ASC, Post.id ASC
You can also define a default ordering on the model itself which can be very useful:-
class Post extends AppModel {
public $order = array('Post.created' => 'asc', 'Post.id' => 'asc');
}

CakePHP Model "Member" is not associated with model "Member"

I am making a PHP forum with CakePHP I am troubles with getting an array of all the members then echoing them in a view, here is my code.
<?php
App::uses('AppModel', 'Model');
class Member extends AppModel {
public $validate = array(
'username' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'password' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'email' => array(
'email' => array(
'rule' => array('email')
),
),
);
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'Topic' => array(
'className' => 'Topic',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
That is my member model here is my MembersController
<?php
App::uses('Controller', 'AppController');
class MembersController extends AppController {
public $components = array('Paginator');
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('profile','login');
}
public function index(){
$this->Paginator->settings['contain'] = array('Member');
$this->set('members', $this->Paginator->paginate());
}
public function profile($id=null) {}
public function login() {
if($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Invalid username or password'));
}
}
}
public function logout() {
$this->redirect($this->Auth->logout());
}
}
And here is my Index view
<div class="row">
<?php foreach ($members as $member): ?>
<?php echo $user[name]; ?>
<?php endforeach; ?>
</div>
When I access example.com/members I get an error saying Model "Member" is not associated with model "Member" [CORE/Cake/Model/Behavior/ContainableBehavior.php, line 342]
Before you ask I have made AppModels actas Containable
class AppModel extends Model {
public $actsAs = array('Containable');
}
Remove that line:
$this->Paginator->settings['contain'] = array('Member');
You're causing a self-join by using this and you haven't defined that in your members model and you dont want that.
This is wrong as well plus your php has a syntax error - missing ' around the name.
<?php foreach ($members as $member): ?>
<?php echo $user[name]; ?>
<?php endforeach; ?>
It should be:
<?php foreach ($members as $member): ?>
<?php echo $member['Member']['name']; ?>
<?php endforeach; ?>
You're struggling with the very basics, I would recommend you to do the blog tutorial or at least read about how data is passed to the view and how model associations work. You can find everything in the book.
Your problem is here:
public function index(){
$this->Paginator->settings['contain'] = array('Member');
$this->set('members', $this->Paginator->paginate());
}
Since paginate is being called from the members controller, it will by default try to paginate Member. Your contain setting is indicating the Member data should also contain the associated model, also named Member. You have no association between Member and itself, hence the error. I'm going to assume that contain setting was a mistake, so simply remove the line $this->Paginator->settings['contain'] = array('Member');
$this->Paginator->settings['contain'] = array('Member'); is your problem. Since the setting are going to be passed as options to find() call on Member model you are effectively asking Member to contain itself. Set 'contain' to array() to prevent any associated records from being fetched.
Though the recommended way it to set property $recursive to -1 in AppModel so that by default no associated records are fetched. Containable is then used to fetch associated records as required.

cakePHP Left Join Main Model in hasMany Relationship Using $options Array causes error

Ok. This must be an easy one for you cakePHP ninjas out there! But my cakePHP skills are still pretty much at the lowest level. So I have 2 Models; Donor Model, and Donations Model.
The setup is as follows :
Donor Model hasMany Donation Model
Donor Model
public $hasMany = array(
'Donation' => array(
'className' => 'Donation',
'foreignKey' => 'donor_id',
'order' => 'Donation.created DESC',
'limit' => 10,
'dependent' => true
)
);
Now in the DonorsController I am using the paginatorComponent's paginate() method. Within my index I have this code
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$donors = $this->Paginator->paginate('Donor',$options);
$this->set('donors',$donors);
}
However this returns an sql Error :
'1054 Unknown column 'joins' in 'where clause' ''
Anyone knows why this is happening, Or if the above code is correct? thanks
Your code is incorrect, please see the below function:
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$this->paginate = $options;
$donors = $this->Paginator->paginate('Donor');
$this->set('donors',$donors);
}

Cannot save associated data with hasMany through (Join Model)

Hi, I am new to cakephp and doing a project on cakephp 2.3.4.
I have to associate product metal class through has many through association . But it doesn't seem to be working.
Model code
class Metal extends AppModel {
public $hasMany = array(
'MetalProduct'
);
}
class Product extends AppModel {
public $hasMany = array(
'MetalProduct'
);
}
App::uses('AppModel', 'Model');
class MetalProduct extends AppModel {
public $belongsTo = array(
'Metal' => array(
'className' => 'Metal',
'foreignKey' => 'metal_id'
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id'
)
);}
My database table names are metal, products and metal_products
I have multiple select option for selecting more than one metal type.
This is how I get the the list of metals
$metals=$this->Metal->find('list');
$this->set(compact('metals'));
FormHelper code for listbox is
<?php echo $this->Form->input('Metal',array('type' => 'select',
'multiple' => true)); ?>
The product is getting saved successfully but the associations are not.
The debug array give me this
$message = array(
'Product' => array(
'category_id' => '517a514b-0eb0-4ec9-b018-0b948620d4f0',
'name' => 'mangalsutra Diamond',
'slug' => 'mangalsutra_diamond',
'description' => '1212',
'Metal' => array(
(int) 0 => '5183cb65-bf90-459c-b22e-0b748620d4f0',
(int) 1 => '5183ce25-c744-433e-b035-0b748620d4f0'
),
'image' => '121212',
'price' => '12121',
'weight' => '12',
'active' => '1',
'category' => 'Mangalsutra'
)
)
I had put my head through walls but no clue why the associations are not getting saved.
The way they say in tutorials it seems easy, but why its not working?
I have doubts that its not saving because the metal array is passed like this
'Metal' => array(
(int) 0 => '5183cb65-bf90-459c-b22e-0b748620d4f0',
(int) 1 => '5183ce25-c744-433e-b035-0b748620d4f0'
),
It should mention 'id''rather than (int) 0 or something.
Also, my database table for metal_products which I have created manually has
id(primary key)
metal_id(foreign key to Metal.id)
product_id(foreign key to Product.id)
Am I doing something wrong with naming conventions or the way database is created?
Please give me correct ans cause anything I tried from others answer is not working
I am saving it via
$this->Product->saveAll($this->request->data, array('deep' => true))
In your model, is all of that in your MetalProduct Model? If so you need to move
class Metal extends AppModel {
public $hasMany = array(
'MetalProduct'
);
}
to the Metal model and
class Product extends AppModel {
public $hasMany = array(
'MetalProduct'
);
}
to the Product Model
Also add your belongsTo to each Model
I see you have a join table. Join tables are usually for HABTM associations. This is a HasMany. You need to use the other options available to define the foreign key relationships in each model as outlined above.
Please see the examples on the Cake Documentation.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Also, if you are unsure of how to set up the models correctly, you can always use the Bake feature of Cakephp to generate the models and code for you in that regard.
book.cakephp.org/2.0/en/console-and-shells/code-generation-with-bake.html
If you are more of a visual learner this video tut should help you through the basics
http://www.youtube.com/watch?v=kJAMifqF5s8
The Relation i generated using Bake looks something like this
class Product extends AppModel {
/**
* hasAndBelongsToMany associations
*/
public $hasAndBelongsToMany = array(
'Metal' => array(
'className' => 'Metal',
'joinTable' => 'metals_products',
'foreignKey' => 'product_id',
'associationForeignKey' => 'metal_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
));
}
class Metal extends AppModel {
/** hasAndBelongsToMany associations */
public $hasAndBelongsToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'metals_products',
'foreignKey' => 'metal_id',
'associationForeignKey' => 'product_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
));
}
/**
* MetalsProduct Model
* #property Product $Product
* #property Metal $Metal
*/
class MetalsProduct extends AppModel {
/* belongsTo associations */
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Metal' => array(
'className' => 'Metal',
'foreignKey' => 'metal_id',
'conditions' => '',
'fields' => '',
'order' => ''
));
}
This worked for me flawlessly.
Hope it works for all.

problem with counterCache of cakePHP

I have in a video sharing website project these models :
class Video extends AppModel {
var $name = 'Video';
var $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'joinTable' => 'videos_tags',
'foreignKey' => 'video_id',
'associationForeignKey' => 'tag_id',
'unique' => true,
)
);
}
class Tag extends AppModel {
var $name = 'Tag';
var $hasAndBelongsToMany = array(
'Video' => array(
'className' => 'Video',
'joinTable' => 'videos_tags',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'video_id',
'unique' => true,
)
);
}
class VideosTag extends AppModel {
var $name = 'VideosTag';
var $belongsTo = array(
'Video' => array(
'className' => 'Video',
'foreignKey' => 'video_id',
),
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
'conditions' => '',
'counterCache' => 'videos_tag_counter'
)
);
}
The counterCache for tags is not working. I don't know why and when I tried to add a beforeSave() callback to videosTag model I found that it doesn't execute when a video get saved (and this video has tags and i find them in the database so how the relation videosTags is saved ? ) !!! can any body explain why this is happening.
Saving a Video with data like this:
array(
'Video' => array(
...
),
'Tag' => array(
'Tag' => array(
...
),
),
);
on the Video model will not trigger a beforeSave callback on the VideosTag model because Cake handles HABTM data without requiring (or even using) the join/with/through model.
There is no built in functionality for what you are trying to achieve, as far as I am aware.
Check out Counter Cache behavior for HABTM relations, might do what you need

Categories