Containable to do show deeper data or join table - cakephp

I have 3 tables: projects, project_reminder_users, project_types.
The relations is as follow:
Project => belong to => ProjectType
hasMany => ProjectReminderUser
ProjectReminderUser => belong to => Project
ProjectType => hasMany => Project
I get all data based on who assigned(ProjectReminderUser) by this
$this->Project->ProjectReminderUser->Behaviors->load('Containable');
$this->paginate = array(
'ProjectReminderUser' => array(
'limit' => $limit,
'contain' => array(
'Project' => array(
'ProjectComment',
'ProjectFile',
),
'User'
),
'conditions' => array(
'User.group_id' => $this->Session->read('Auth.User.group_id'),
'ProjectReminderUser.user_id' => $this->Session->read('Auth.User.id'),
'Project.project_status_id' => PROJECT_STATUS_OPEN,
),
'order' => 'Project.due_date',
)
);
$this->set('myTasks', $this->paginate('ProjectReminderUser'));
and the result look like this
array(
'ProjectReminderUser' => array(
'id' => '96',
'user_id' => '1',
'project_id' => '46'
),
'Project' => array(
'id' => '46',
'project_type_id' => '9',
'contact_id' => null,
'company_id' => null,
'subject' => 'Test Modified Field',
'description' => 'Test Modified Field',
'ProjectFile' => array(
(int) 0 => array(
'id' => '19',
'project_id' => '46',
'user_id' => '6',
'file_path' => '46_bhbinary_xmm_1728.jpg',
'notes' => null,
'created' => '2013-11-26 18:37:49'
),
),
'ProjectComment' => array(
)
),
'User' => array(
'password' => '*****',
'id' => '1',
'group_id' => '1',
'email' => 'xxx#xxxxx.com',
'first_name' => 'xxxx',
'deleted' => false,
'displayName' => 'xxxxx'
)
)
In the result There is data for Project.project_type_id, but I would like to have more detail of that. So I can show it as name instead of number. maybe like ProjectType.name instead.
How can I achieve this, so I can sort it in the view? something like this
$this->Paginator->sort('ProjectType.name', 'Type');

The problem is that Paginator doesn't play nice with deep model associations. I think though if rather than using Cake's methods for doing model associations, you instead do your joins manually, you may be able to work out the sorting as you want. See the below discussion.
http://sinkpoint.railsplayground.net/cakephp-pagination-deep-sort-and-habtm/
Worst comes to worse, you may have to also rewrite the model's paginate function to deal with sorting how you need it to.

How about
'contain' => array(
'Project' => array(
'ProjectComment',
'ProjectFile',
'ProjectType',
),
'User'
),
Looks like you did not contain "ProjectType"

Related

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.

saveAll: save new data for related model or use existing record

I'm trying to update data about many shipments with one form.
Shipment model belongsTo Shippingaddress model and Shippingaddress hasMany Shipment.
In interface user can use his previously saved Shippingaddress from select field or fill small form and add new one.
Currently example structure of data which are posted to controller looks like this:
array(
(int) 0 => array(
'Shipment' => array(
'id' => '223',
'shippingaddress_id' => '8',
)
),
(int) 1 => array(
'Shipment' => array(
'id' => '224',
'shippingaddress_id' => '',
),
'Shippingaddress' => array(
'recipient' => 'some name',
'address' => 'some addres data',
'zip' => '00-555',
'city' => 'some city',
)
),
(int) 2 => array(
'Shipment' => array(
'id' => '225',
'shippingaddress_id' => '12',
)
)
)
It would be nice to save Shiippingaddress data and update all Shipments in one transaction with
$this->Shipment->saveAll($data).
Is it possible?
I was trying many combination of posted array structure but without success.
Your $data structure should looks like this:
$data = array(
'Shippingadress' => array(
'0' => array(
'id' => '8',
'Shipment' => array(
'id' => '223'
)
),
'1' => array(
'recipient' => 'some name',
'address' => 'some addres data',
'zip' => '00-555',
'city' => 'some city'
'Shipment' => array(
'id' => '224'
)
),
'2' => array(
'id' => '12',
'Shipment' => array(
'id' => '255'
)
),
)
);
For save use saveMany:
$this->Shippingadress->saveMany($data, array('deep' => true) );
You don't need to specify shippingadress_id to empty string because Cake already takes care of it and put all relevant foreign keys based on the relationship of your models.

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