cakephp find(all) optimization - cakephp

I am using cakephp 2.5.2
I have 5 tables
1- users
2- projects
3- tags
4- project_tags
5- images
a user can have many projects
a project can have many (tags,images)
I have 2 questions
$projects = $this->Project->find('all',array('conditions'=>array('User.id'=>$this->Auth->user('id'))));
debug($projects);exit();
this gives
array(
(int) 0 => array(
'Project' => array(
'id' => '1',
'name' => 'project 1'
),
'User' => array(
'password' => '*****',
'id' => '1',
'name' => 'User 1',
'email' => 'user1#gmail.com'
),
'Image' => array(),
'ProjectTag' => array(
(int) 0 => array(
'id' => '1',
'project_id' => '1',
'tag_id' => '1'
),
(int) 1 => array(
'id' => '2',
'project_id' => '1',
'tag_id' => '8'
),
(int) 2 => array(
'id' => '3',
'project_id' => '1',
'tag_id' => '6'
),
(int) 3 => array(
'id' => '4',
'project_id' => '1',
'tag_id' => '10'
),
(int) 4 => array(
'id' => '5',
'project_id' => '1',
'tag_id' => '4'
)
)
),
(int) 1 => array(
'Project' => array(
'id' => '2',
'name' => 'project 2'
),
'User' => array(
'password' => '*****',
'id' => '1',
'name' => 'user 1',
'email' => 'user1#gmail.com'
),
'Image' => array(),
'ProjectTag' => array(
(int) 0 => array(
'id' => '6',
'project_id' => '2',
'tag_id' => '1'
),
(int) 1 => array(
'id' => '7',
'project_id' => '2',
'tag_id' => '8'
),
(int) 2 => array(
'id' => '8',
'project_id' => '2',
'tag_id' => '4'
)
)
)
)
I need Project & ProjectTag array while do not want user and images array
I tried recursive -1,0,1,2 but could not get what I needed
My Question Number 2 is
How can I find projects which has tag = 'html'
$projects = $this->Project->find('all',array('conditions'=>array('Tag.tag'=>'HTML')));
it says
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Tag.tag' in 'where clause'

First off, just a note. ALWAYS use $recursive=-1;. It's ideal just to set this in the AppModel, then never mess with recursive again. If you want to get additional data, use CakePHP's Containable Behavior, not recursive.
Overall issue - you cannot add conditions against a recursive (or contained) model, which is what you appear to be doing in both of your questions. If you want to condition against a model other than the one you're doing the find on, you need to either use JOINs or swap your query to run on the other model.
Answer to Question 1
Change this:
$projects = $this->Project->find('all',array(
'conditions'=>array(
'User.id'=>$this->Auth->user('id')
)
));
To this:
$projects = $this->Project->find('all',array(
'conditions'=>array(
'user_id'=>$this->Auth->user('id') // <-- notice this
),
'contain' => array(
'ProjectTag' => array(
'Tag' // <-- optional, as you didn't mention you needed
)
)
));
Answer to Question 2:
There are a number of ways to do this. I would suggest this one using joins:
$projects = $this->Project->find('all', array(
'fields' => array(
'Project.*',
'Tag.*'
),
'joins'=>array(
array(
'table' => 'tags',
'alias' => 'Tag',
'type' => 'inner',
'conditions' => array(
'Tag.project_id = Project.id',
'Tag.tag' => 'HTML'
)
)
)
));
Another way would be to swap your query to run on Tags and just use contain to contain the projects for that tag. I like the join better because if you ever way to extend it to retrieve projects for more than just one tag, your data will still be in an easy-to use manner, as opposed to Containable, which would put your projects in different arrays under their corresponding tag.

If you want to optimize your queries in CakePHP, turn off recursive (set it to -1), then use the containable behavior to pick and choose exactly which data you need.
As for your specific query, associated models are by default gotten by left joins, which means if you did your query from projects, you will get all projects plus any tags they have that have "html" in their tag field. The easiest way to fix this problem, is to do your search from tags instead:
$tags = $this->Tag->find('first', array('conditions' => array('tag' => 'html'), 'contain' => array('Project')));
This will get the tag that has 'html' for its tag field, and all the projects associated with it.

Related

cakephp set:extract array generated by find joins

I have cakephp 2.8 project, I have an array returned by Model->find('all',array('joins')) which is something like this
array(
(int) 0 => array(
'Result' => array(
'id' => '1',
'student_id' => '1',
),
'User' => array(
'fullname' => 'Alam'
)
),
(int) 1 => array(
'Result' => array(
'id' => '2',
'student_id' => '11',
),
'User' => array(
'fullname' => 'Student 1'
)
)
);
I want this array as below
array(
(int) 0 => array(
'id' => '1',
'student_id' => '1',
'fullname' => 'Alam'
),
(int) 1 => array(
'id' => '2',
'student_id' => '11',
'fullname' => 'Student 1'
)
);
I tried Set::extract('/Result/.'); , Set::extract('/Result/User/.'); and other variations but I am not getting desired result.
I know I can do it by foreach loop but I want if it can be done by some builtin php or cakephp function.
I haven't managed to use only one function, but, maybe this will do the trick for you:
$t = Hash::mergeDiff(
Hash::extract($inputArray, '{n}.Result'),
Hash::extract($inputArray, '{n}.User')
);
debug($t);

CakePHP Containable multidimensional array hierarchy

If I do this:
$cdata = $this->Party->find('first',
array('contain' => array(
'Person' => array(
'Employee' => array(
'Volunteer'),
'Title'))));
Debugger::dump($cdata, 10);
I get this:
array(
'Party' => array(
'id' => '9',
'save_bit' => true
),
'Person' => array(
'id' => '5',
'title_id' => '1',
'first_name' => 'bob',
'middle_name' => '',
'last_name' => 'brown',
'is_active' => false,
'date_of_birth' => '1999-07-07',
'gender' => 'M',
'party_id' => '9',
'Title' => array(
'id' => '1',
'title' => 'Ms',
'description' => ''
),
'Employee' => array(
'id' => '5',
'person_id' => '5',
'Volunteer' => array(
'id' => '5',
'employee_id' => '5'
)
)
)
)
But I really want to get this:
array(
'Party' => array(
'id' => '9',
'save_bit' => true,
'Person' => array(
'id' => '5',
'title_id' => '1',
'first_name' => 'bob',
'middle_name' => '',
'last_name' => 'brown',
'is_active' => false,
'date_of_birth' => '1999-07-07',
'gender' => 'M',
'party_id' => '9',
'Title' => array(
'id' => '1',
'title' => 'Ms',
'description' => ''
),
'Employee' => array(
'id' => '5',
'person_id' => '5',
'Volunteer' => array(
'id' => '5',
'employee_id' => '5'
)
)
)
)
)
This way the array is following the natural hierarchy of my database tables and also works with my website forms for saving and editing which I have declared like so:
echo $this->Form->input('Party.Person.first_name');
echo $this->Form->input('Party.Person.is_active');
Is it possible to do this with Containable, if so how? Thanks!
As already mentioned in my comment, you cannot change the way how containable formats the data, it will be formatted to the CakePHP conventions depending on your associations.
Changing the format would have to be done manually after the find operation, but there's actually no need to do that, instead stick to the conventions with regards to associations and form helper usage, that way CakePHP can automatically glue everything together as needed.
Assuming the association is Party hasMany Person, you should simply use Person.0.first_name in the form, that way the helper can properly populate the field, and when passed to Model::saveAll() or Model::saveAssociated() CakePHP can determine how things need to be saved based on the associations defined on your models.
See Saving Related Model Data (hasOne, hasMany, belongsTo) for more information.

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.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

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'
)
)))
));

Resources