I have got stuck on Multilevel associated tables in Cake PHP code.
I have the following models
Guardian who has many students and the various students have their studentfees. When I create a guardian with 2 students, an associated 2 row must be created for StudentFees table. Im successful in adding 2 students when adding a guardian, but I dont know how to add the 2 rows of fees for the student. My code is as below.
class Guardian extends AppModel {
public $name = 'Guardian';
public $recursive =2;
public $hasMany = array(
'Student' => array(
'className' => 'Student',
'dependent' => true
)
);
}
class Student extends AppModel {
public $name = 'Student';
public $hasMany = array(
'StudentFee' => array(
'className' => 'StudentFee',
'dependent' => true
)
);
}
class StudentFee extends AppModel {
public $name = 'StudentFee';
public $belongsTo = array(
'Student' => array(
'className' => 'Student',
'dependent' => true
)
);
}
Pl help me to save the studenFee details too. I use SaveAssociated function that saves guardian and Student details.
If I understood you correctly, this should do the trick:
Model::saveAll() should take care of it for you and select the appropriate method, saveMany or saveAssociated. Moreover, it will automatically set your foreignKeys so everything is neatly inserted into the database.
$this->Guardian->saveAll(array('Guardian' => array(
[...],
'Student' => array(
0 => array(
[here's your first Student],
'StudentFee' => array(
0 => array(
[here's your first StudentFee]
)
)
)
)
)));
can use saveAll and saveAssociated methods.
You can try using this method, which i always use in such situations :
$this->Studentfee->create();
$this->Studentfee->set(array(
'field1' => 'value',
'field2' => 'value'
));
$this->Studentfee->save();
and you can put in a loop for all student fees
Related
I just cant get data from 2 tables in a HABTM relationship and I cant get the answer for these cakephp docs (again). Sorry for asking the question on something that should be explained in the docs.
This should be a simple but I keep getting undefined index and the relationship I set up seems in accordance with the docs. I have another post on a more complicated matter but this issue needs to be isolated .
I dont know how to get related data from the lessons table and the students table via this lessons-students HABTM table.
I want all the lessons a student does. I want the name of the student from the student table and lesson details from the lesson table which has lesson_id as the common link in this lessons-students table. Sounds simple but I cant do it.
public $hasAndBelongsToMany = array(
'Student' => array(
'className' => 'Student',
'joinTable' => 'lessons_students',
'foreignKey' => 'lesson_id',
'associationForeignKey' => 'student_id',
'unique' => 'keepExisting',
)
);
class LessonsController extends AppController {
....
$this->Lesson->recursive = -1;
$options['joins'] = array(
array('table' => 'lessons_students',
'alias' => 'LessonsStudent',
'type' => 'LEFT',
'conditions' => array(
'Lesson.id = LessonsStudent.lesson_id',
)
),
array('table' => 'students',
'alias' => 'Student',
'type' => 'LEFT',
'conditions' => array(
'LessonsStudent.student_id=Student.id',
)
),
);
$options['conditions'] = array('Student.id' => 2);
// $Item->find('all', $options);
$student=$this->set( 'student',$this->Lesson->find('all', $options));
In the view I get the error undefined index Student
<?php
// debug($student);
foreach ($student as $item2):
echo '<td>'. $item2['Lesson']['id'].'</td>';
echo '<td>'. $item2['Student']['id'].'</td>';
http://stackoverflow.com/questions/17250215/cant-get-all-user-data-from-habtm-in-cakephp
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
If you want to use model association you need to remove $this->Lesson->recursive = -1; because it disable any associations defined in model. And than you can remove $options['joins'].
If you have defined many association in your Lesson model and don't want to fetch all of it use unbindModel() or use Containable behavior
$this->Lesson->Behaviors->load('Containable');
$this->Lesson->contain('Student');
If you need to do find many times in many actions it's better to enable Containable in model itself
class Lesson extends AppModel {
public $actsAs = array('Containable');
}
so you can avoid $this->Lesson->Behaviors->load('Containable'); line;
I have an issue trying to retrieve associated model data from another associated model.
I have Donor Model which is associated with BloodGroup Model (belongsTo/hasMany rel).
Now, I also have a Donation Model which is associated with Donor Model (again, belongsTo/hasMany rel).
From the Donation Model, I want to retrieve data from the BloodGroup Model.
Currently only the Donor related data is retrieved. The idea is that I do not add a blood group field inside the donation, but map the blood group of a donation, on the fly, through the donor that made the donation!
I have been checking out the containable Behavior but I am not sure if this is how I can do it! It seems that it is used to 'contain' and filter data from associated models rather than to extend associations.
Any help appreciated very much, as always!
[EDIT]
DONOR MODEL
class Donor extends AppModel{
public $belongsTo = array(
'BloodGroup'=> array(
'className' => 'BloodGroup'
),
'DonorType' => array(
'className' => 'DonorType'
)
);
public $hasMany = array(
'Donation' => array(
'className' => 'Donation',
'foreignKey' => 'donor_id',
'order' => 'Donation.created DESC',
'limit' => 10,
'dependent' => true
)
);
DONATION MODEL
public $belongsTo = array(
'Donor' => array(
'className' => 'Donor',
'counterCache' => true,
)
);
From the Donation Controller :
public $paginate = array(
'order' => array("Donation.d_date" => 'desc'),
'limit' => 10
);
$this->Paginator->settings = $this->paginate;
$donations = $this->Paginator->paginate('Donation');
$this->set('donations',$donations)
Result retrieved :
[donations] => Array
(
[0] => Array
(
[Donation] => Array
(
[id] => 1
[donor_id] => 4
etc..
)
[Donor] => Array
(
[id] => 4
[name] => ...
)
)
)
It's not clear what data you actually want to get, but to answer what appears to be your hope:
"From the Donation Model, I want to retrieve data from the BloodGroup
Model."
Because there's a related path, you can get BloodGroup data, from the Donation model like this:
$this->Donor->BloodGroup->find('all');
Or by using Containable:
$this->find('all', array(
'contain' => array(
'Donor' => array(
'BloodGroup'
)
)
));
There's a lot of options - it just depends on what data you need.
For details on how I use find()s in my Model while still paginating, see this answer: https://stackoverflow.com/a/6577042/673664
(there are likely a lot of ways to do this - this is just the one I use, and works great for me)
If the BloodGroup has many Donor and if Donor has many Donation, You can retrieve Donation data from BloodGroup but you have to set the recursive to 2 ($this->Donation->recursive = 2;) before you use find method. If you want output blood group data from donation. Try that.
hi guys i am new to cakephp
now i'm facing a little big problem
here is the situation
i hava a shop that hasMany Catalogs which is related to many products each product has a category
i want to fetch them all just by getting the shop
i dont know how to do it
trying to use hasMAny gives me just the ids
instead is there any way to get shop inside it array of catalogs each catalog has Product's array which has one array of category
thank you
Ok, I'm on my computer now :).
In ShopModel:
class ShopModel extends AppModel {
public $hasMany = array(
'Catalog' => array(
// binding params here...
),
);
}
In CatalogModel:
class CatalogModel extends AppModel {
public $hasMany = array(
'Product' => array(
// binding params...
),
),
}
... and this goes on...
If you don't want to get excessive data in all actions, you should set in AppModel:
class AppModel extends Model {
public $recursive = -1;
}
In the controller action where you call the find function with associations:
$this->Shop->Behaviors->load('Containable');
$big_array = $this->Shop->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Catalog' => array(
'Product' => array(
// etc, you get the point :)
),
),
),
));
It is also nice to declare the $belongsTo associations too, so you can access anything from anywhere, something like this:
$this->Catalog->Behaviors->load('Containable');
$big_array = $this->Catalog->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Product' => array(
// ...
),
'Shop' => array(
// ...
),
),
));
EDIT
I see you have a Product->Category relation that i guess would be defined with $belongsTo. If you do a query like the one above, you will get a lot of duplicate queries (same category in many products). You can use $this->Category->find('list') but very often I find this inappropriate as it is returning only one field (I would be grateful if someone knows a way how can I get more fields with the list type). For this purpose, my workaround is making a custom function in the Category model like this:
class Category extends AppModel {
public function getSorted ($options = array()) {
$temp= $this->find('all', $options);
$output = array();
foreach ($temp[$this->alias] as $row) {
$output[$this->alias][$row['id']] = $row;
}
unset($temp);
return $output;
}
}
Then in the controller I would declare two arrays, the big one without category association and the category list one:
$this->loadModel('Category');
$this->set('categories', $this->Category->getSorted());
This way, I can get the needed category row by category id wherever i need it in the view.
Do not use CakePHP association they are not good handling complex relationships, you will later face problems with....Instead create all your join queries on the fly...I am giving you one example below:
Create one function inside Shop model and join catalog and product as shown below:
$options = array(
'conditions' => array('Product.id'=>9),
'joins' => array(
array(
'alias' => 'Catalog',
'table' => 'catalogs',
'type' => 'LEFT',
'conditions' => array(
'Catalog.product_id = Product.id',
),
),
array(
'alias' => 'Product',
'table' => 'products',
'type' => 'LEFT',
'conditions' => array(
'Shop.id = Product.shop_id',
),
)
),
'fields' => array('Product.*'),
'group' => array('Product.id')
);
$returnData = $this->find('all',$options);
This will make coding little easier and you can escape from associations!
I'm working on an already existing cakephp 1.3 project and I needed to add a new table to the database. I have this in my controller:
$conditions = array('ShootingPlacement.person_id' => $id, 'Email.person_id' => $id, 'Email.shooting_placement_id' => 'ShootingPlacement.id');
$shootingPlacements = $this->ShootingPlacement->find('all', compact('conditions'));
And it's giving me this error:
Warning (512): SQL Error: 1054: Unknown column 'Email.person_id' in 'where clause' [CORE/cake/libs/model/datasources/dbo_source.php, line 684]
And ths is the query it's trying to create:
SELECT `ShootingPlacement`.`id`, ... FROM `shooting_placements` AS `ShootingPlacement`
LEFT JOIN `people` AS `Person` ON (`ShootingPlacement`.`person_id` = `Person`.`id`)
LEFT JOIN `shootings` AS `Shooting` ON (`ShootingPlacement`.`shooting_id` = `Shooting`.`id`)
WHERE `ShootingPlacement`.`person_id` = 123688 AND `Email`.`person_id` = 123688 AND `Email`.`shooting_placement_id` = 'ShootingPlacement.id'
ORDER BY `lastname` ASC
Obviously my controller code is wrong, but I'm not sure how to relate the Email table to the ShootingPlacement one. I think my models are correct. So far if I have this:
$conditions = array('ShootingPlacement.person_id' => $id);
$shootingPlacements = $this->ShootingPlacement->find('all', compact('conditions'));
It will retrieve the rows from Shooting, ShootingPlacement and Person, I want Email to be there too. Email has 2 foreign keys: one from ShootinPlacement and one from Person.
These are the models, the only one I created is Email, the rest where working correctly.
class Email extends AppModel
{
var $name = 'Email';
var $belongsTo = array
(
'Person' => array
(
'className' => 'Person',
'foreignKey' => 'person_id'
),
'ShootingPlacement' => array
(
'className' => 'ShootingPlacement',
'foreignKey' => 'shooting_placement_id'
)
);
}
class ShootingPlacement extends AppModel
{
var $name = 'ShootingPlacement';
var $belongsTo = array
(
'Person' => array
(
'className' => 'Person',
'foreignKey' => 'person_id',
'order' => 'lastname ASC'
),
'Shooting' => array
(
'className' => 'Shooting',
'foreignKey' => 'shooting_id'
)
);
}
class Person extends AppModel
{
var $name = 'Person';
var $belongsTo = array
(
'PersonOrigin' => array
(
'className' => 'PersonOrigin',
'foreignKey' => 'person_origin_id'
)
);
var $hasMany = array
(
'ShootingPlacement' => array
(
'className' => 'ShootingPlacement',
'foreignKey' => 'person_id',
'dependent' => false
)
);
}
class Shooting extends AppModel
{
var $name = 'Shooting';
var $belongsTo = array
(
'ShootingLocation' => array
(
'className' => 'ShootingLocation',
'foreignKey' => 'shooting_location_id'
),
'Emission' => array
(
'className' => 'Emission',
'foreignKey' => 'emission_id'
)
);
}
What I need on the view is to loop through the ShootingPlacement variable and I need it to contain the Email table data for that specific id of ShootingPlacement and Person (As you see in the query, Person and ShootingPlacement are in a relationship already, I only need there to be Email too)
You should be very careful with the relationship you're after. From a quick glance at some of these answers, they seem to suggest you simply add a join to the Email model into your Person model and rely on the conditions of your find to ensure your query doesn't ransack your server's memory.
I'm going to assume that first of all, you want this Email relationship to be implicit in all your queries on Person, otherwise you could simply specify the join on each query you wanted it for. In this case, you definitely want to link it using model relationships.
Your code shows that Shooting and ShootingPlacement (presume this is a model to model mapping relationship) both belong to two models. Incidentally, Shooting belongsTo Emission - which we haven't seen here yet. I assume this isn't applicable to the current scenario.
Now, let's assume off the bad that because your Email table has foreign keys, it will be a hasOne relationship, rather than a hasMany - so that's what you need to link it by. I'm going to link it to the ShootingPlacement model because this is the model you are querying, so it should be the central point at which models are joined around it. Structure wise, because everything seems to originate from your Person model, I would have to suggest you query that model instead. But the way it's set up so far will allow you to query from nearly anywhere and still retrieve mostly the same results bar a few model names and table aliases.
Purely because your foreign key between Email and ShootingPlacement has a different name, and CakePHP 1.3 doesn't handle this very well, I'm also going to suggest you don't use a foreign key, instead putting it into the relationship as conditions.
class ShootingPlacement extends AppModel
{
var $name = 'ShootingPlacement';
var $actsAs = array('Containable');
var $hasOne = array(
'Email' => array(
'className' => 'Email',
'foreignKey' => false,
'conditions' => array(
'Email.shooting_placement_id = ShootingPlacement.id',
'Email.person_id = ShootingPlacement.person_id'
)
)
);
var $belongsTo = array (
'Person' => array (
'className' => 'Person',
'foreignKey' => 'person_id',
'order' => 'lastname ASC'
),
'Shooting' => array (
'className' => 'Shooting',
'foreignKey' => 'shooting_id'
)
);
}
I've also added the containable behaviour in there. This allows you to control from each query which associated models you'd like to return with your primary model results. It will default to all, but can be handy when you only want something specific and/or for memory reasons (these kinds of queries can destroy your server memory pretty quickly if you don't limit them or specify only the field names you want to return).
Now when you create your Email model, I wouldn't suggest complicating this mess of entangled models any further by linking it back to ShootingPlacement again. As you've said, it also has a foreign key to the Person model. So you might want to do exactly the same thing as above for your Person model (changing the conditions to reflect the Person foreign key of course). This way your model is a little more flexible; it will still join to ShootingPlacement and Person, and will also allow you to query it seperately if required without the other associated models.
Documentation
CakePHP 1.3 Model Associations
CakePHP 1.3 Containable Behaviour
See also
This article on Stack
In your model add containable behavior
class Email extends AppModel {
var $name = 'Email';
var $actsAs = array('Containable');
var $belongsTo = array
(
'Person' => array
(
'className' => 'Person',
'foreignKey' => 'person_id'
),
'ShootingPlacement' => array
(
'className' => 'ShootingPlacement',
'foreignKey' => 'shooting_placement_id'
)
);
}
Just write the below code in your controller.
$this->ShootingPlacement->recursive = 2;
$this->ShootingPlacement->contain = array(
'Shooting',
'Person' => array(
'Email'
)
);
$conditions = array(
'ShootingPlacement.person_id' => $id,
'Email.shooting_placement_id' => 'ShootingPlacement.id'
);
$shootingPlacements = $this->ShootingPlacement->find('all', compact('conditions'));
Hope this helps you.
Add a $hasOne relation to Person model with Email like below
var $hasOne = array(
'Email' => array(
'className' => 'Email',
'foreignKey' => 'person_id' // Column defined for person ids in Email table
)
);
Then add
$this->ShootingPlacement->recursive = 2;
OR
you can simply use joins in cakephp to join email model. Refer cakephp joining tables
You need to link your model ShootingPlacement with "Email" with which you call it.
class ShootingPlacement extends AppModel
var $name = 'Shooting';
var $hasMany= array
(
'Email' => array
(
'className' => 'Email',
'foreignKey' => 'yourfk'
),
);
}
And uses it s very powerful ContainableBehavior !
exemple :
$contain=array('Email'=>array('fields'=>array('id','...')));
$conditions=array('ShootingPlacement.id'=>$yourId);
$this->ShootingPlacement->attachBehaviros('Containable');
$this->ShootingPlacement->find('all',$conditions);// your will retrieve yoru SHootingItem + Emails linked
This would provide the required join:
$conditions = array('ShootingPlacement.person_id' => $id, 'Email.person_id' => $id, 'Email.shooting_placement_id' => 'ShootingPlacement.id');
$joins = array(
array(
'table' => 'emails',
'alias' => 'Email',
'type' => 'LEFT',
'conditions' => array('Email.shooting_placement_id = ShootingPlacement.id')
)
);
$shootingPlacements = $this->ShootingPlacement->find('all',
array(
'conditions' => $conditions,
'joins' => $joins
)
);
I use CakePHP 2.2.2
I have 3 tables: restaurants, kitchens and kitchens_restaurants - join table for HABTM.
In Restaurant model I have:
public $hasAndBelongsToMany = array(
'Kitchen' =>
array(
'className' => 'Kitchen',
'joinTable' => 'kitchens_restaurants',
'foreignKey' => 'restaurant_id',
'associationForeignKey' => 'kitchen_id',
'unique' => true,
'conditions' => '',
'fields' => 'kitchen',
'order' => '',
'limit' => '',
'offset' => '',
),
The problem is that I have separate controller for my main page in which I need to retrieve data from this models with complex conditions.
I added
public $uses = array('Restaurant');
to my main page controller and here comes the part where I need your advices.
I need to select only those restaurants where kitchen = $id.
I've tried to add
public function index() {
$this->set('rests', $this->Restaurant->find('all', array(
'conditions' => array('Restaurant.active' => "1", 'Kitchen.id' => "1")
)));
}
and I got SQLSTATE[42S22]: Column not found: 1054 Unknown column in 'where clause' error.
Obviously I need to fetch data from "HABTM join table" but I don't know how.
TLDR:
To retrieve data that's limited based on conditions against a [ HABTM ]'s association, you need to use [ Joins ].
Explanation:
The code below follows the [ Fat Model/Skinny Controller ] mantra, so the logic is mostly all in the model, and just gets called from a controller.
Note: You don't need all those HABTM parameters if you follow the [ CakePHP conventions ] (which it appears you are).
The below code has not been tested (I wrote it on this site), but it should be pretty darn close and at least get you in the right direction.
Code:
//Restaurant model
public $hasAndBelongsToMany = array('Kitchen');
/**
* Returns an array of restaurants based on a kitchen id
* #param string $kitchenId - the id of a kitchen
* #return array of restaurants
*/
public function getRestaurantsByKitchenId($kitchenId = null) {
if(empty($kitchenId)) return false;
$restaurants = $this->find('all', array(
'joins' => array(
array('table' => 'kitchens_restaurants',
'alias' => 'KitchensRestaurant',
'type' => 'INNER',
'conditions' => array(
'KitchensRestaurant.kitchen_id' => $kitchenId,
'KitchensRestaurant.restaurant_id = Restaurant.id'
)
)
),
'group' => 'Restaurant.id'
));
return $restaurants;
}
//Any Controller
public function whateverAction($kitchenId) {
$this->loadModel('Restaurant'); //don't need this line if in RestaurantsController
$restaurants = $this->Restaurant->getRestaurantsByKitchenId($kitchenId);
$this->set('restaurants', $restaurants);
}
There is a much cleaner way than the solution provided by Dave.
First you need to set a reverse HABTM Relationship between Restaurant and Kitchen in the Kitchen Model.
Than you just make a find for the Kitchen you are interested in (id = 1) and you will get the associated restaurants, using Containable Behavior for filtering by Restaurant fields.
$this->Restaurant->Kitchen->Behaviors->attach('containable'); // Enable Containable for Kitchen Model
$this->Restaurant->Kitchen->find('first', array(
'recursive' => -1 // don't collect other data associated to Kitchen for performance purpose
'conditions' => array('Kitchen.id' => 1),
'contain' => array('Restaurant.active = 1')
));
Source
You can not need use [join], because use have setting [ HABTM ]'s association
Kitchen model hasAndBelongsToMany Restaurant model so that you can code as bellow
KitchensControllers
<?php
public function index() {
$this->Kitchen->recursive = 0;
$kitchens = $this->Kitchen->find('all', array('contain' => array('Restaurant')));
$this->set('kitchens', $kitchens);
}
?>
Good luck!