I'm fairly new to cakePHP and I'm trying to build a simple webapp.
I have a 3 Models: Property, Operation and Category.
My relationships are:
Category -> hasMany -> Property
Operation -> hasMAny -> Property
each property has a foreign key for the category (cat_id) and for the operation (op_id).
What I want to do is show each property (in the corresponding view) with the NAME of the category and operation (the field is 'name' in the respective tables), and not the ID's. How can I do this?
UPDATE:
An example of a desired output would be:
ID category operation description ....
1 House Sell a house ....
What I have now is
ID category operation description ....
1 2 3 a house ....
2 and 3 being the respective ID's of 'house'(category, cat_id) and 'sell'(operation, op_id)
This is the code of the Category model:
class Category extends AppModel{
public $hasMany = array(
'Property'=>array(
'foreignKey' => 'cat_id'
));
}
Thank you in advance.
properties(id, name, category_id, operation_id)
categories(id, name)
operations(id, name)
when you do a find on the property table with a recursive = 1 or 2 you will get all the related data automatically
Found the solution! Easier than I thought.
First, The use of 'hasMany' was not ideal. What I did was to use the relation "belongsTo" in the Property Model:
class Property extends AppModel{
var $name = 'Property';
public $belongsTo = array(
'Category' => array(
'foreignKey' => 'idCat'
),
'Operation' => array(
'foreignKey' => 'idOp'
)
);
}
Now the magic: Whenever I do a find() in the PropertiesController, What I've got is a two-dimensional array which contains the property data, along with the data of the tables it belongs to. :
Array
(
[Property] => Array
(
[id] => 1
)
[Category] => Array
(
[id] => 2
[name] => House
)
[Operation] => Array
(
[id] => 3
[name] => Sell
)
)
Now in the view, instead of, for instance, ['Property']['idCat'] I just use ['Category']['name'] and everything works.
Thanks for the help!
P.S.: sorry for any mis-understanding, not a native speaker.
Related
I have three Mysql tables:
Brands ( brand names , brand Ids)
cars ( car names,car ids, brand Ids)
statuss ( car Ids, each car status).
I am trying to pull the brand names from the brand table but I have the cars IDs. How can I do that see my code below.
Controller.ctp
$this->loadModel('car');
$cars = $this->car>find('all',array('limit' => 6, 'order' => array('car.id' => 'asc')));
$this->set('cars', $cars);
$this->loadModel('status');
$cars = $this->status>find('all',array('limit' => 6, 'order' => array('status.id' => 'asc')));
$this->set('statuss', $statuss);
View.ctp
App::import('Controller', 'cars');
$carsCont = new carsController;
App::import('Controller', 'brands');
$brandCont = new brandsController;
foreach($statuss as $status)
{
$car_info = $carsCont->get_car_info($status['status']['car_id']);
$car_name = $car_info['car_name'];
$car_id = $status['status']['car_id'];
$brand_list = $brandCont -> get_brand_name($status['car']['brand_id']); <---- this is not working
echo $brand_list['brand']['brand']; echo $car_name;
}
CakePHP controllers are not designed or meant to be instantiated directly like you are doing.
Rather, you should be making use of the associations that cakephp gives you.
Based on what you've said, you've got Statuses belongsTo Cars belongsTo Brands. Set those relations up in your model files (as documented in the book: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html)
If you then use the containable behavior (http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html) you'd be able to do something like (In your status controller)
$statuses = $this->Statuses->find('all', [
'contain' => ['Cars' => 'Brands']
]);
and you should have all the data that you need.
Edit: Based on your additions, change your cars query to
$cars = $this->car>find('all',array('limit' => 6, 'order' => array('car.id' => 'asc'), 'contain' => ['Brands']));
But, make sure Cars BelongsTo Brands, and you've attached the Containable behaviour.
I'm trying to get only one HABTM relationship when using paginate using CakePHP 2.3.
Right now I'm getting this result:
Array
(
[0] => Array
(
[Ticket] => Array ( .....)
[User] => Array ( .....)
[Priority] => Array ( .....)
[Status] => Array(....)
[Attachment] => Array(....)
....
)
[1] => ....
[2] => ....
)
By using:
$this->Ticket->recursive = 0;
$this->paginate = array(
'limit' => 20
);
I am using $this->Ticket->recursive = 0; because I'm only interested in the belongsTo relationships with other tables, but at the same time, I would like to get only one of the HABTM relationships.
I know I could achieve it adding each of the single relationships in the contain array, like so:
$this->Ticket->recursive = 0;
$this->paginate = array(
'limit' => 20,
'contain' => array(
'Ticket`, `User`, `Priority`, `Status`, `Attachment`, `Tags`...
)
);
But I have plenty of tables and I was wondering if there's any way to do it without having to name every single table in the contain array.
I've also tried to use the recursive array inside the contains one like so:
//getting the filtered tickets
$this->paginate = array(
'limit' => 20,
'contain' => array(
'Ticket' => array('recursive' => 1),
'Tag' //this is the hasMany relationship
)
);
But it didn't work. Is there any way to achieve this without having to name all the tables to make it more simple to maintain?
Ok, as it seems there's no solution for it, I managed to do it by getting the associated models of the type belongsTo and then adding the model name of the HABTM relationship:
$associations = $this->Ticket->getAssociated('belongsTo');
$associations[] = 'Tag';
//getting the filtered tickets
$this->paginate = array(
'limit' => 20,
'contain' => $associations
);
I have two models with the relationship Advisor belongsTo Room, Room hasMany Advisor. Advisor has a foreignKey constraint in the database (Advisor.room_id) which points to a specific room. The default value for this is the NULL value (representing an advisor without a room).
Suppose I had an Advisor, with room_id set to n. I now wish to unassign the the nth room from Advisor - using a select field, I can reset room_id to NULL, with the following request->data structure:
[Room] => Array
(
[name] => TestRoom2
[type] => single suite
[id] => 4
)
[Advisor] => Array
(
[0] => Array
(
[id] => 14
[room_id] =>
[name] => foo
)
)
However, when I attempt to do this through the use of checkboxes with the same generated $this->request->data, MySQL refuses to update the NULL value.
In addition, it seems that changing the value of room_id explicitly in request->data in the second case has no effect. However, if I were to change Advisor.0.name to hax, (by modifying request->data directly) the name field does save.
I save via calling $this->Room->saveAll($request->data, array('deep' => true)) - this is true both in the case of the select field and the checkbox.
I am generating the series of checkboxes by repeatedly calling the Form helper:
$count = 0;
// $key is the Advisor id, and $attributes is an array of the form
// array('name' => (string), 'disabled' => (bool))
foreach($options['advisorList'] as $key => $attributes) {
$form[] = $this->Form->hidden(sprintf('Advisor.%s.id', $count), array('value' => $key));
$form[] = $this->Form->input(sprintf('Advisor.%s.room_id', $count), array(
'type' => 'checkbox',
'label' => $attributes['name'],
'disabled' => $attributes['disabled']));
$count++;
}
Moreover, if room_id has already been set to NULL, there is no problem setting room_id - except, room_id will be set regardless if the checkbox is checked or not.
Any help would be greatly appreciated - thanks!
Solved! This has to do with funky cakePHP $model->saveAll() on hasMany relationships.
In order to correctly update this form, all advisors that may be changed in saveAll() must unset their relationship to their respective rooms and saved in the database
foreach($copyOfData['Advisor'] as $advisor) {
$advisor['room_id'] = null;
$this->Advisor->save(array('Advisor' => $advisor));
}
Only after this has been done, can we call $this->Advisor->saveAll($this->request->data, array('deep' => true)) and have saveAll() behave as expected.
I have a HABTM relationship between two tables: items and locations, using the table items_locations to join them.
items_locations also stores a bit more information. Here's the schema
items_locations(id, location_id, item_id, quantity)
I'm trying to build a page which shows all the items in one location and lets the user, through a datagrid style interface, edit multiple fields at once:
Location: Factory XYZ
___________________________
|___Item____|___Quantity___|
| Widget | 3 |
| Sprocket | 1 |
| Doohickey | 15 |
----------------------------
To help with this, I have a controller called InventoryController which has:
var $uses = array('Item', 'Location'); // should I add 'ItemsLocation' ?
How do I build a multidimensional form to edit this data?
Edit:
I'm trying to get my data to look like how Deceze described it below but I'm having problems again...
// inventory_controller.php
function edit($locationId) {
$this->data = $this->Item->ItemsLocation->find(
'all',
array(
"conditions" => array("location_id" => $locationId)
)
);
when I do that, $this->data comes out like this:
Array (
[0] => Array (
[ItemsLocation] => Array (
[id] => 16
[location_id] => 1
[item_id] => 1
[quantity] => 5
)
)
[1] => Array (
[ItemsLocation] => Array (/* .. etc .. */)
)
)
If you're not going to edit data in the Item model, it probably makes most sense to work only on the join model. As such, your form to edit the quantity of each item would look like this:
echo $form->create('ItemsLocation');
// foreach Item at Location:
echo $form->input('ItemsLocation.0.id'); // automatically hidden
echo $form->input('ItemsLocation.0.quantity');
Increase the counter (.0., .1., ...) for each record. What you should be receiving in your controllers $this->data should look like this:
array(
'ItemsLocation' => array(
0 => array(
'id' => 1,
'quantity' => 42
),
1 => array(
...
You can then simply save this like any other model record: $this->Item->ItemsLocation->saveAll($this->data). Adding an Item to a Location is not much different, you just leave off the id and let the user select the item_id.
array(
'location_id' => 42, // prepopulated by hidden field
'item_id' => 1 // user selected
'quantity' => 242
)
If you want to edit the data of the Item model and save it with a corresponding ItemsLocation record at the same time, dive into the Saving Related Model Data (HABTM) chapter. Be careful of this:
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
And:
3.7.6.5 hasAndBelongsToMany (HABTM)
unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
Re: Comments/Edit
I don't know off the top of my head if the FormHelper is intelligent enough to autofill Model.0.field fields from a [0][Model][field] structured array. If not, you could easily manipulate the results yourself:
foreach ($this->data as &$data) {
$data = $data['ItemsLocation'];
}
$this->data = array('ItemsLocation' => $this->data);
That would give you the right structure, but it's not very nice admittedly. If anybody has a more Cakey way to do it, I'm all ears. :)
I've got a series of Post models that hasAndBelongsToMany Media models. In certain function calls inside of the Post model, I don't need to retrieve the entire list of Media models. However, when I use the following code:
$this->unbindModel( array('hasAndBelongsToMany' => array('Media')) );
// Rebind to get only the fields we need:
$this->bindModel(
array('hasAndBelongsToMany' => array(
'Media' => array(
'className' => 'Media',
'joinTable' => 'media_posts',
'foreignKey' => 'post_id',
'associationForeignKey' => 'media_id',
'limit' => 1,
'fields' => array('Media.type', 'Media.path', 'Media.title')
)
)
)
);
$this->find('all', $params);
This limit only works on one of the first retrieved Post model and all following Post models have no associated Media:
Array
(
[0] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
[0] => Array
(
[type] => photo
[path] => ''
[title] => ''
)
)
)
[1] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
)
)
)
Any suggestions would be great. Thanks!
why not use the containable behaviour
// you would probably want the next line in the app_model ot be able to use it with all models
$this->Post->actsAs = array('Containable')
$params['conditions'] = array(
);
$params['contain'] = array(
'Media' => array(
'fields' => array(
'type', 'path', 'title'
),
'limit' => 1
)
);
$this->Post->find('all', $params);
EDIT:
Just tried that and got this sql (Module <-> Tag):
SELECT `Module`.`id` FROM `modules` AS `Module` WHERE 1 = 1
and
SELECT `Tag`.`id`, `ModulesTag`.`module_id`, `ModulesTag`.`tag_id`
FROM `tags` AS `Tag`
JOIN `modules_tags` AS `ModulesTag`
ON (`ModulesTag`.`module_id` IN (1, 2, 3, 4) AND `ModulesTag`.`tag_id` = `Tag`.`id`)
WHERE `Tag`.`belongs_to` = 'Module'
ORDER BY `Tag`.`name` ASC
LIMIT 1
obviously that cannot return the wanted result, as you would have to do a query for each Module result (which then again would result in way too many queries).
As a conclusion I would return all Tags (in my example) as the overhead in too many result rows is better than the overhead of too many queries..
Cake fetches all the Habtm-related records in one batch query and then assembles them into the results array afterwards. Any additional conditions you specify in the association will be used as is in the query, so it'll look something like this:
SELECT … FROM Media WHERE Media.id in (1, 2, 3, …) LIMIT 1
So it'll only retrieve a single HABTM model.
There's no apparently easy solution for this. Maybe you could think about the original premise again and why the "first" (LIMIT 1) record is supposedly the right one, maybe you can find a different condition to query on.
Failing that, you could rebind your models so Media has a hasMany relationship to medias_posts, the pivot table. For hasMany and belongsTo queries, Cake automatically does JOIN queries. You could use a GROUP BY clause then, which would give you the desired result:
SELECT … FROM Media JOIN medias_posts … GROUP BY medias_posts.post_id
You might also want to experiment with passing the 'join' parameter with the query, to achieve that effect without extensive rebinding.
$this->Media->find('all', array('join' => array(…), …));
Try this:
$this->yourModel->hasAndBelongsToMany['Media'] = false; // or null
And then set your HABTM association manually
$this->yourModel->hasAndBelongsToMany['Media'] = array(........);
Or simply modify the association without nulling it:
$this->yourModel->HABTM['Media']['fields'] = array(....)
CakePHP has a very powerful tool for this containable behaviour