Cakephp find all query on multiple models - cakephp

I try the following to get al the Articles That belong to MenuItem 6 and have an Article content_type of 'blog'. It does find all the articles with content_type='blog'. But I only want it to return the Article if it belongs to Menuitem 7. And now it return an empty value for MenuItem when not 7.
How can I accomplish that it'll only load the articles from MenuItem 7?
MenuItem has a HABTM relationship with Article
code:
$d=$this->Article->find('all' , array('contain' => array(
'MenuItem' => array(
'conditions' => array(
'MenuItem.id ' => 7,
),
'fields'=>'id'
),
'Tag'=>array(
'conditions'=>array(
'Tag.name'=>'tag1'
),
'fields'=>'name'
)
),
'conditions'=>array('Article.content_type' => 'blog'),
'fields'=>array('id','content_type'),
'recursive'=>1
));
debug($d);
array:
array(
(int) 10 => array(
'Article' => array(
'id' => '15',
'content_type' => 'blog'
),
'Tag' => array(),
'MenuItem' => array()
),
(int) 11 => array(
'Article' => array(
'id' => '16',
'content_type' => 'blog'
),
'Tag' => array(),
'MenuItem' => array(
(int) 0 => array(
'id' => '7',
'MenuItemsArticle' => array(
'id' => '18',
'title' => '',
'article_id' => '16',
'menu_item_id' => '7'
)
)
)
)
)

I think your best bet for this type of find condition is to modify your models to use hasMany through (The Join Model). Here's an example I tested that does what you want:
Article.php
class Article extends AppModel {
public $hasMany = array('ArticleMenuItem');
}
MenuItem.php
class MenuItem extends AppModel {
public $hasMany = array('ArticleMenuItem');
}
ArticleMenuItem.php
class ArticleMenuItem extends AppModel {
public $useTable = 'articles_menu_items';
public $belongsTo = array(
'Article',
'MenuItem'
);
}
The Find Call
$articles = $this->ArticleMenuItem->find('all', array(
'conditions' => array(
'menu_item_id' => 7,
'Article.content_type' => 'blog'
),
'contain' => array(
'Article' => array(
'fields' => array(
'id',
'title'
)
),
'Tag'
)
));
Here's what it produces:
array(
(int) 0 => array(
'ArticleMenuItem' => array(
'article_id' => '1',
'menu_item_id' => '7'
),
'Article' => array(
'id' => '1',
'content_type' => 'blog',
'title' => 'test blog'
)
),
(int) 1 => array(
'ArticleMenuItem' => array(
'article_id' => '4',
'menu_item_id' => '7'
),
'Article' => array(
'id' => '4',
'content_type' => 'blog',
'title' => 'another'
)
)
)
And here's the query it generates:
SELECT `ArticleMenuItem`.`article_id`, `ArticleMenuItem`.`menu_item_id`, `Article`.`id`, `Article`.`content_type`, `Article`.`title` FROM `caketest`.`articles_menu_items` AS `ArticleMenuItem` LEFT JOIN `caketest`.`articles` AS `Article` ON (`ArticleMenuItem`.`article_id` = `Article`.`id`) WHERE `menu_item_id` = 7 AND `Article`.`content_type` = 'blog'
I've set $recursive = -1 in my AppModel as well. I would suggest doing the same since you are using the containable behavior, it's much more efficient because it only pulls back the data that you need. :)
Hope this helps, any questions just let me know.

Related

How to get rid of null associated model in cakephp 2.x

I am trying to get the item types that Order.Item.ItemType.show_type = 1. I have written the query but I want to show only Items that their ItemType.show_type = 1 not all items.
$brief = $this->Order->find('first', array(
'fields' => array(
'Order.*'
),
'conditions' => array(
'Order.order_id' => $orderId,
),
'contain' => array(
'Item' => array(
'fields' => array(
'Item.*', 'CHAR(64 + Item.num) AS letter'
),
'conditions' => array(
'Item.deleted' => 0,
),
'ItemType' => array(
'conditions' => array(
'ItemType.show_type' => 1
),
)
),
)
));
The query shouldn't show Item id = 25741
Associations:
// Order
public $hasMany = array(
'BriefInstalment' => array(
'foreignKey' => 'order_id'
)
);
// Item Model
public $belongsTo = array(
'Order',
'ItemType' => array(
'type' => 'inner'
)
);
// ItemType Model
public $hasMany = array('Item');
Print:
array(
'Order' => array(
'order_id' => '67817',
'service' => '',
),
'Item' => array(
(int) 0 => array(
'id' => '25741',
'order_id' => '67817',
'num' => '2',
'item_type_id' => '8',
'name' => '3-5 titles active',
'deleted' => false,
'ItemType' => array(), // <= how to remove this empty model
'Item' => array(
(int) 0 => array(
'letter' => 'B'
)
)
),
(int) 1 => array(
'id' => '25742',
'order_id' => '67817',
'num' => '3',
'item_type_id' => '2',
'name' => '1,000 pro active',
'deleted' => false,
'ItemType' => array(
'id' => '2',
'name' => 'Part Instalment',
'show_type' => true,
'deleted' => false
),
'Item' => array(
(int) 0 => array(
'letter' => 'C'
)
)
)
)
)
This could not be done using Countaible behaviour, but iwth the joins method, set the recursive to -1
$brief = $this->Order->find('first', array(
'recursive' => -1,
'fields' => array(
'Order.*'
),
'conditions' => array(
'Order.order_id' => $orderId,
),
'joins' => array(
array(
'table' => 'items',
'alias' => 'Item',
'type' => 'inner',
'conditions' => array(
'Order.id = Item.order_id'
)
),
array(
'table' => 'item_types',
'alias' => 'ItemType',
'type' => 'inner',
'conditions' => array(
'ItemType.id = Item.item_type_id'
)
),
)
));
You have to check if the table names are correct and also the foreign keys names.
Another solution would be to go through your results, and unset the empty ones
foreach($brief as $k => $v){
foreach($v['Item'] as $kk => $vv){
if(empty($vv['ItemType'])){
unset($brief[$k]['Item'][$kk];
}
}
}
debug($brief);

CakePHP: why does format of data retrieved depend on which associated model is called

I have a site with the usual sort of sellers, items and images. The models look like this:
class Seller extends AppModel {
$hasMany = array('Item');
...
}
class Item extends AppModel {
$belongsTo = array('Seller')
$hasMany = array('Image');
...
}
class Image extends AppModel {
$belongsTo = array('Item');
...
}
My problem is that when I retrieve a list of Items, the data comes out in a different format depending on whether I am getting a list of all Items (to display to the public) in the ItemsController, or the details of a Seller and their Items in the SellersController.
Briefly Item->find('all') retrieves an array of
array( 'Item' => array( <item stuff> ),
'Images' => array of array( <image stuff> ) );
whereas the Seller->find('first', ...) retrieves the Items as
array( 'Seller' => array( <seller stuff> ),
'Item' => array of array ( <item stuff>
'Images' => array of array( <image stuff> ) );
In the 2nd case the Images element is nested within the Item part. Having the data formatted so differently makes it harder to re-use View code. Also my Item afterFind() won't work properly has problems when the Images bit isn't where it expects it to be.
QUESTION
Is there a simple way to prevent the 'Images' part being nested within the 'Item' part in the 2nd situation?
I am using Cake 2.4
Formats in More Detail
All items:
// in ItemsController.php
debug($this->Item->find('all'));
produces this:
array(
(int) 0 => array(
'Item' => array(
'id' => '1',
'title' => 'tom item 1',
'seller_id' => '1'
),
'Seller' => array(
'id' => '1',
'name' => 'tom'
),
'Images' => array(
(int) 0 => array(
'id' => '1',
'item_id' => '1',
'path' => 'tom_1_1'
),
(int) 1 => array(
'id' => '2',
'item_id' => '1',
'path' => 'tom_1_2'
)
)
),
(int) 1 => array(
'Item' => array(
'id' => '2',
'title' => 'tom item 2',
'seller_id' => '1'
),
'Seller' => array(
'id' => '1',
'name' => 'tom'
),
'Images' => array(
(int) 0 => array(
'id' => '3',
'item_id' => '2',
'path' => 'tom_2_1'
),
(int) 1 => array(
'id' => '4',
'item_id' => '2',
'path' => 'tom_2_2'
)
)
),
...
A single Seller + associated Items:
// In SellersController.php
debug($this->Seller->find('first',
array('conditions' => array('id' => $id), 'recursive' => 2)));
produces:
array(
'Seller' => array(
'id' => '1',
'name' => 'tom'
),
'Item' => array(
(int) 0 => array(
'id' => '1',
'title' => 'tom item 1',
'seller_id' => '1',
'Seller' => array(
'id' => '1',
'name' => 'tom'
),
// Look! 'Images' is WITHIN 'Item !!!
'Images' => array(
(int) 0 => array(
'id' => '1',
'item_id' => '1',
'path' => 'tom_1_1'
),
(int) 1 => array(
'id' => '2',
'item_id' => '1',
'path' => 'tom_1_2'
)
)
),
(int) 1 => array(
'id' => '2',
'title' => 'tom item 2',
'seller_id' => '1',
'Seller' => array(
'id' => '1',
'name' => 'tom'
),
'Images' => array(
(int) 0 => array(
'id' => '3',
'item_id' => '2',
'path' => 'tom_2_1'
),
(int) 1 => array(
'id' => '4',
'item_id' => '2',
'path' => 'tom_2_2'
)
)
),
....
This is the expected and correct behavior as Image is not directly associated with Seller, but with Item.
Imagine, how would you determine the proper association when Image would be defined on the same level as Item? That would indicate an association with Seller which doesn't exist.
If you want a different structure, format it in your afterFind callback or wherever it's appropriate. However, I wouldn't recommend trying to work against the CakePHP standards. In case applicable, adapt your view code.
Your question is not clear to me. If just wants to prevent Image to coming in 2nd case, try this
$this->Seller->Item->unbindModel(array(
'hasMany' => array('Image')
));
$result = $this->Seller->find('first',
array('conditions' => array('id' => $id), 'recursive' => 2)));
debug($result);

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.

Containable Nested models

I have a few nested models which I'm trying to load using Containable behaviour in CakePHP.
Most of it works fine.
However, 1 model that has a hasMany relation only returns 1 object:
$article = $this->News->find('first',array(
'conditions'=>array('News.id'=>$id),
'contain' => array(
'Newslayout',
'Newspicture'=> array(
'NewspicturesProduct' => array(
'Product' => array(
'Brand',
'Category'
)
)))
));
The object only being loaded once is the relation Newspicture hasMany NewspicturesProduct
When I log the queries, I get the following:
SELECT `NewspicturesProduct`.`id`, `NewspicturesProduct`.`x`, `NewspicturesProduct`.`y`, `NewspicturesProduct`.`product_id`, `NewspicturesProduct`.`newspicture_id`, `NewspicturesProduct`.`w`, `NewspicturesProduct`.`h` FROM `edclondon`.`newspictures_products` AS `NewspicturesProduct` WHERE `NewspicturesProduct`.`newspicture_id` = 3
Which gives me 3 results in phpMyAdmin. But only 1 in CakePHP's debug:
'Newspicture' => array(
(int) 0 => array(
'id' => '3',
'news_id' => '2',
'newspicture_file_path' => '5022443f-ddf8-4115-ae57-618e9d60b047.jpg',
'newspicture_file_size' => '1232546',
'order' => null,
'NewspicturesProduct' => array(
'id' => '1',
'x' => '0.180664',
'y' => '0.295312',
'product_id' => '3',
'newspicture_id' => '3',
'w' => '0.286133',
'h' => '0.478125',
'Product' => array(
'id' => '3',
//....
'Brand' => array(
'id' => '6',
//...
),
'Category' => array(
'id' => '6',
//....
)
)
)
)
When retrieving the Newspictures object rather then retrieving the News object, I do get all 3 NewspicturesProduct objects.
It seems to me that the code corresponding the query you showed should be:
$article = $this->News->find('first',array(
'contain' => array(
'Newslayout',
'Newspicture'=> array(
'NewspicturesProduct' => array(
'conditions'=>array('NewspicturesProduct.newspicture_id'=>'3')
'Product' => array(
'Brand',
'Category'
)
)))
));
and not the one you gave...
It seems you need 3 records from NewspicturesProduct. If that then you can try:
$article = $this->News->find('first',array(
'contain' => array(
'Newslayout',
'Newspicture'=> array(
'NewspicturesProduct' => array(
'conditions'=>array(
'limit'=> 3
),
'Product' => array(
'Brand',
'Category'
)
)))
));

Cakephp: associations of associations in "fields"

a post has a user (belongsTo) and a user has a profile (hasOne). Normally everything works: I can access from Post to User and from User to Profile.
From Post, all works using a generic Find. The result is more or less this (I deleted some keys, just for example):
array(
'Post' => array(
'id' => '1',
'category_id' => '1',
'user_id' => '1',
'title' => 'Example',
'text' => '',
),
'User' => array(
'password' => '*****',
'id' => '1',
'group_id' => '1',
'username' => 'mirko',
'status' => 'active',
'Profile' => array(
'id' => '1',
'user_id' => '1',
'first_name' => 'Mirko',
'last_name' => 'Pagliai',
),
)
)
The problem comes when I use "fields", when I want to extract only a few fields.
For example, this works:
'fields' => array('id', 'title', 'User.id')
and the result is:
array(
'Post' => array(
'id' => '1',
'title' => 'Articolo di prova :-)'
),
'User' => array(
'id' => '1'
)
)
But I don't understand, always using "fields", how to access Profile.
These don'yt work:
'fields' => array('id', 'title', 'User.id', 'Profile.id')
'fields' => array('id', 'title', 'User.id', 'User.Profile.id')
I always get this error "Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'User.Profile.id' in 'field list'".
What's wrong? Thanks.
EDIT:
I'm not using the Containable behavior. Maybe is this the problem? Is it essential in this case?
An example, using paginate:
$this->paginate = array(
'conditions' => $conditions,
'fields' => array('id', 'title', 'User.id', 'Profile.id'),
'limit' => 10,
'recursive' => 2,
);
EDIT2:
thanks to #Hoff, I am close to the solution. But there is still something wrong (and Containable is very useful!).
In AppModel.php I added:
...
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
...
But this doesn't work:
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'User' => array(
'fields' => array('id'),
'Profile' => array(
'fields' => array('id', 'full_name'),
),
),
'Category' => array(
'fields' => 'title',
),
),
'fields' => array('id', 'user_id', 'category_id', 'title', 'created', 'modified', 'published'),
'limit' => 10,
);
Cause I get:
array(
(int) 0 => array(
'Post' => array(
'id' => '1',
'user_id' => '1',
'category_id' => '1',
'title' => 'Articolo di prova :-)',
'created' => '2012-06-21 18:46:00',
'modified' => '0000-00-00 00:00:00',
'published' => true
),
'Category' => array(
'title' => 'prova',
'id' => '1'
),
'User' => array(
'id' => '1'
)
)
)
but if I add any field to User (email, username, password, etc.), this works:
'User' => array(
'fields' => array('id', 'username'),
'Profile' => array(
'fields' => array('id', 'full_name'),
),
),
And I get:
array(
(int) 0 => array(
'Post' => array(
'id' => '1',
'user_id' => '1',
'category_id' => '1',
'title' => 'Articolo di prova :-)',
'created' => '2012-06-21 18:46:00',
'modified' => '0000-00-00 00:00:00',
'published' => true
),
'Category' => array(
'title' => 'prova',
'id' => '1'
),
'User' => array(
'id' => '1',
'username' => 'mirko',
'Profile' => array(
'id' => '1',
'full_name' => 'Mirko Pagliai'
)
)
)
)
Instead of using the 'recursive' key, I highly suggest using the containable behavior. The documentation on the behavior can be found here. I would suggest applying the behavior in your AppModel so you don't need to add it to all your models. In Model/AppModel.php:
App::uses('Model', 'Model');
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Then, for your pagination:
$this->paginate = array(
'conditions' => $conditions,
'fields' => array('id', 'title'),
'limit' => 10,
'contain' => array(
'User' => array(
'fields' => array('id'),
'Profile' => array(
'fields' => array('id')
)
)
)
);

Resources