Displaying the 4th level of an array in CakePHP using relationship - cakephp

I have table project, screenshot, sub_screenshot, and user
I want to achieve this kind of diagram. Its like getting the 4th level.
[Model]
Array
(
[0] => Array
(
[Project] => Array
(
[id] => 2
)
[Screenshot] = Array
(
[0] => Array
(
[Screenshot] => Array
(
[project_id] => 2
[id] => 1
)
[SubScreenshot] = Array
(
[0] => Array
(
[id] = 1
[project_id] = 2
[screenshot_id] = 1
)
[1] => Array
(
[id] = 2
[project_id] = 2
[screenshot_id] = 1
)
)
)
[1] => Array
(
[Screenshot] => Array
(
[project_id] => 3
[id] => 1
)
[SubScreenshot] = Array
(
[0] => Array
(
[id] = 1
[project_id] = 3
[screenshot_id] = 1
)
[1] => Array
(
[id] = 2
[project_id] = 3
[screenshot_id] = 1
)
[2] => Array
(
[id] = 2
[project_id] = 3
[screenshot_id] = 1
)
)
)
)
[User] => Array
(
[id] => 1
)
)
)
Regards,

Use the containable behavior. I've had some questions about it lately too. It would be much more efficient that setting recursive to 4 since you would only be including the models that you need.
Follow these instructions to setup the behavior on your models. http://book.cakephp.org/view/1323/Containable
Then, you should be able to perform a find and get the structure you want.
$this->Project->find(
'all',
array(
'conditions' => array(
//put your conditions in here
),
'contain' => array(
'User',
'Screenshot',
'Screenshot.SubScreenshot'
'Scrrenshot.SubScreenshot.User'
)
);
There are a few ways to use the model syntax. You can add arrays to the contained models to add conditions and/or further depth.

To give you some idea I suggest you to use the afterFind() callback
http://book.cakephp.org/view/1050/afterFind
After the find set the User wherever you want making another find with the User model.
I don't know if exists any configuration to modify the $recursive without a modification in the core.
EDIT
You can create a generic afterFind in AppModel when you can check if you have the $recursive=4 and copy the core functionality of the relationships and generate them.

Related

cakephp find all primary column as key

When I fire this:
$this->model->find('all',array());
I get an array with the data of the model:
Array
(
[0] => Array
(
// some data
)
[1] => Array
(
// some data
)
[2] => Array
(
// some data
)
...
)
Now is it possible instead of 0,1,2 to get the id as key for each of the data?
For example:
Array
(
[365] => Array
(
[model] => Array
(
[id] => 365
// some data
)
)
[442] => Array
(
[model] => Array
(
[id] => 442
// some data
)
)
[1000] => Array
(
[model] => Array
(
[id] => 1000
// some data
)
)
...
)
I know that find('list') can do something like that but only for 2 fields max (one key and one value).
you can build it maybe this code can do it :
$final_array = array();
foreach($returned_array as $k => $v){
$final_array[$v['id']] = $v;
}
print_r($final_array);
It's still an after-find operation but take a look at Set::combine (http://book.cakephp.org/2.0/en/core-utility-libraries/set.html) as a more elegant solution. Something like:
$newArray = Set::combine($yourArray, '{n}.Model.id');
/* $newArray now looks like:
Array
(
[365] =>
[442] =>
[1000] =>
)
*/
(more or less just the the docs example) might work in your case.

cakephp linked model not showing in hasmany

I have three Models in cakephp
//Union
public $hasMany = 'Member';
and
//Member
public $hasOne = 'Post';
where as post contains post names
Now the problem is when I use:
$this->Union->findById(1);
in Union controller it shows linked Members but not name of posts
[Union] => Array
(
[id] => 1
[Name] => Dawa vyapar mandal
[created] => 2014-03-31 14:08:12
)
[Member] => Array
(
[0] => Array
(
[id] => 1
[Name] => Ashish
[post_id] => 1
[union_id] => 1
[created] => 2014-03-31 14:11:02
)
[1] => Array
(
[id] => 2
[Name] => Ashu
[post_id] => 1
[union_id] => 1
[created] => 2014-07-01 15:01:15
)
)
)
so how to achieve Post Model inside Member Model?
You have to make the model to be recursive to the level upto which you need data.
In your case, you need the data upto 2nd level, so make recursive as -
$this->Union->recursive = 2;
And after that find your data -
$this->Union->findById(1)

CakePHP, how can I find all products associated with sub categories when parent category given?

I've got two tables that are setup to use the tree behavior, manufacturers, and categories.
Products can only belong to one category and only one manufacturer, however some manufacturers(child) are owned by other manufacturers(parent), and likewise some categories(child) are a subcategory of another(parent).
I want to do the following:
given a category id (parent), find all products in subcategories
given a manufacturer id (parent), find all products in child manufacturers
I have tried the following (in products controller):
$conditions['Product.category_id'] = $this->Product->Category->children($id,false,'id');
$this->paginate = array(
'conditions' => $conditions,
'limit' => 21
);
$products = $this->paginate('Product');
$this->set(compact('products'));
but it gives me this:
WHERE `Product`.`category_id` IN (Array, Array, Array, Array, Array, Array)
if I do a print_r I can see it is grabbing the information I need(see below), but how can I get to it, and is there better way to do this?
Array
(
[Product.category_id] => Array
(
[0] => Array
(
[Category] => Array
(
[id] => 11
)
)
[1] => Array
(
[Category] => Array
(
[id] => 12
)
)
[2] => Array
(
[Category] => Array
(
[id] => 23
)
)
[3] => Array
(
[Category] => Array
(
[id] => 24
)
)
[4] => Array
(
[Category] => Array
(
[id] => 25
)
)
[5] => Array
(
[Category] => Array
(
[id] => 26
)
)
)
)
The query is failing because it expects an array containing only the category-ids, like this:
$conditions['Product.category_id'] = array(1,4,5,6);
You can achieve this by 'extracting' those values from your array using Hash::extract() (or Set::extract() if you're using CakePHP 1.3)
$categoryIds = $this->Product->Category->children($id,false,'id');
$conditions['Product.category_id'] = Hash::extract($categoryIds, '{n}.Category.id);
Read the documentation on the Hash Utility here:
http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::extract

CakePHP same array from the direct and associated model

I have simple model relationship in CakePHP 1.3 with Categories -> Products
Category hasMany Products
There is slight difference between the data arrays which I get in the different controllers. The Product data is in the main product array when getting as associated model in the Categories controller and is separated when getting it in Products.
For Example to get 'Product1'
in Categories - $category['Product'][0]['title']
and in Products - $product[0]['Product']['title']
I would like to use same element for displaying the products. It does not matter which array scheme will be used just to be the same. And where is the right place to make the modification? I can modify those arrays after getting them, but don't think that it is the best option.
When I am in the Categories controller and get a category I get this:
// $this->Category->findById('12');
Array
(
[ProductCategory] => Array
(
[id] => 12
[title] => Category 1
[updated] => 2013-02-24 10:06:15
[created] => 2013-02-24 10:06:15
)
[Product] => Array
(
[0] => Array
(
[id] => 4
[parent_id] => 12
[title] => Product1
[updated] => 2013-02-24 10:17:01
[created] => 2013-02-24 09:12:59
)
[1] => Array
(
[id] => 6
[parent_id] => 12
[title] => Product2
[updated] => 2013-02-24 10:16:54
[created] => 2013-02-24 09:13:53
)
)
And when getting all the products inside the Products controller:
// $this->Product->find('all');
Array
(
[0] => Array
(
[Product] => Array
(
[id] => 10
[parent_id] => 12
[title] => Product1
[updated] => 2013-02-24 10:16:42
[created] => 2013-02-24 09:16:35
)
)
[1] => Array
(
[Product] => Array
(
[id] => 8
[parent_id] => 12
[title] => Product2
[updated] => 2013-02-24 10:16:47
[created] => 2013-02-24 09:15:39
)
)
)
)
One of your finds is a find('all') and the other is a findById() (which uses find('first')).
Both of these return data in a different format, since find('first') knows you only want one item, and find('all') is an unknown set of item(s).
Just use find('all') for both, but set your limit based on whether you need only one or more than one. Then, your data will be returned exactly the same.
Which Controller you retrieve your data from has no effect on the data returned. Which MODEL however, does - so just make sure you're doing your find from the same model.
Eg.
//in your ProductsController
$this->Product->find('all');
//in your CategoriesController
$this->Category->Product->find('all');
// in some other controller
$this->loadModel('Product);
$this->Product->find('all');
PS - BUT it's better if you don't do your "finds" in your Controller - make a method in your Model, and call it from your Controller(s) so instead of $this->Product->find(), it would be $this->Product->getProducts() (or whatever you want to call it). (read more about "fat models, skinny controllers" for reasons/examples...etc).
Dave is right, the difference is the method you're using... Even though you claim that associated data is always merged, your find on the 'Product' model is NOT associated data, so the format WILL always be different.
I've been here for a short while and I've already noticed that Dave knows his stuff. :)
I agree with the fat models/skinny controllers paradigm for clean, efficient code.
If you changed:
<?php
$this->Category->contain(array(
'Product'
));
$this->Category->find('all',
array(
'conditions' => array(
'Category.id' => $id // 12 for your OP.
),
'limit' => 1
)
);
?>
Should give you:
<?php
array
(
[0] => array
(
'Category' => array
(
[id] => 12
[title] => Category 1
[updated] => 2013-02-24 10:06:15
[created] => 2013-02-24 10:06:15
),
'Product' => array
(
[0] => array
(
...
),
[1] => array
(
...
)
)
)
)
?>
Please correct me if I am mistaken, thanks!
Or if you want "Products" to look like:
<?php
'Product' => array
(
[0] => array
(
'Product' => array
(
...
)
)
)
?>
when fetching data from the category model, you would need to fetch the associated data manually, e.g.:
<?php
$this->Category->contain();
$cats = $this->Category->find('all');
foreach ($cats as &$cat) {
$this->Category->Product->contain(); // You have to contain for each find.
$cat['Product'] = $this->Category->Product->find('all',
array(
'conditions' => array(
'Product.category_id' => $cat['Category']['id']
)
)
);
}
?>

Save multiple records for one model in CakePHP

I would like to save several records for one model. This would have been done pretty easily with saveAll() if it hadn't been for a problem:
I have a notification form, where I select multiple users from a <select> and two fields, for subject and content respectively. Now, when I output what $this->data contains, I have:
Array([Notification] => Array
(
[user_id] => Array
(
[0] => 4
[1] => 6
)
[subject] => subject
[content] => the-content-here
)
)
I've read on Cake 1.3 book, that in order to save multiple records for a model, you have to have the $this->data something like:
Array([Article] => Array(
[0] => Array
(
[title] => title 1
)
[1] => Array
(
[title] => title 2
)
)
)
So, how do I 'share' the subject and content to all selected users?
First off, this database design needs to be normalized.
It seems to me like a Notification can have many Users related to it. At the same time, a User can have many Notifications. Therefore,
Introduce a join table named users_notifications.
Implement the HABTM Relationship: Notification hasAndBelongsToMany User
In the view, you can simply use the following code to automagically bring up the multi-select form to grab user ids:
echo $this->Form->input('User');
The data that is sent to the controller will be of the form:
Array(
[Notification] => Array
(
[subject] => subject
[content] => contentcontentcontentcontentcontentcontent
),
[User] => Array
(
[User] => Array
(
[0] => 4
[1] => 6
)
)
)
Now, all you have to do is called the saveAll() function instead of save().
$this->Notification->saveAll($this->data);
And that's the Cake way to do it!
Those values have to be repeat like this
Array([Notification] => Array(
[0] => Array
(
[user_id] => 4
[subject] => subjects
[content] => content
)
[1] => Array
(
[user_id] => 6
[subject] => subject
[content] => contents
)
)
)
$this->Notification->saveAll($data['Notification']); // should work
If you don't pass any column value, this cake will just ignore it
You're going to have to massage your form's output to suit Model::saveAll. In your controller:
function action_name()
{
if ($this->data) {
if ($this->Notification->saveMany($this->data)) {
// success! :-)
} else {
// failure :-(
}
}
}
And in your model:
function saveMany($data)
{
$saveable = array('Notification'=>array());
foreach ($data['Notification']['user_id'] as $user_id) {
$saveable['Notification'][] = Set::merge($data['Notification'], array('user_id' => $user_id));
}
return $this->saveAll($saveable);
}
The benefit here is your controller still knows nothing about your model's schema, which it shouldn't.
In fact, you could probably redefine saveAll in your model, which hands off correctly formatted input arrays to parent::saveAll, but handles special cases itself.
There might be a more cakey way of doing this, but I've used this kind of technique: First, change the form so that you get an array like this:
Array(
[Notification] => Array
(
[subject] => subject
[content] => contentcontentcontentcontentcontentcontent
),
[selected_users] => Array
(
[id] => Array
(
[0] => 4
[1] => 6
)
)
)
(Just change the multiselect input's name to selected_users.id)
Then loop through the user ids and save each record individually:
foreach( $this->data[ 'selected_users' ][ 'id' ] as $userId ) {
$this->data[ 'Notification' ][ 'user_id' ] = $userId;
$this->Notification->create(); // initializes a new instance
$this->Notification->save( $this->data );
}

Resources