Unable to Save BelongsTo Data Using Checkboxes in CakePHP - php

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.

Related

MySQL result multiple arrays

i.e : i have 2 tables
Product ( id, name )
Photo ( id, name, photo_id )
And I need to get result in array like this:
array(
'id' => 1,
'name' => product,
'photos' => array(
array('id' => 1, 'name' => 'photo1')
array('id' => 2, 'name' => 'photo2')
)
}
Is it possible in PHP using clear SQL?
I know that is possible to get 2 arrays and connect it but I have many records and I dont want to wase time to quering.
You have to add a foreign_key in your photo table "product_id".
Then create a method getPhotos() in your Product class with will collect all photos for your product.
Is it possible in PHP using clear SQL?
Not in a single SQL call. With a single call, this is the closest you can get:
array(
'id' => 1,
'name' => product,
'photo_id' => 1,
'photo_name' => 'photo1')
),
array(
'id' => 1,
'name' => product,
'photo_id' => 2,
'photo_name' => 'photo2')
)
Your only choice for the format you want is to run queries separately or to combine them into the data structure you want.
As mentioned, this is not possible with SQL. SQL is based on the relational model which is a 1-Normal-Form data model. That means, the result relation is also flat (no nested relations in a relation).
However, there are good frameworks which generate intermediary models in your corresponding target language (e.g. Python, Java, ...) that circumvent the impression of a flat data model. Check for example Django.
https://docs.djangoproject.com/en/1.8/topics/db/models/
Moo

CakePHP join with comma separated ids

Here I want to join two table with comma separated ids
For example my data is like:
[Restaurant] => Array
(
[RST_ID] => 171
[RST_NAME] => oneone
[RST_IMAGE] =>
[RST_CAT_ID] => 2,4,6
[RST_CT_ID] => 27
[RST_IS_TOP] => 3
[RST_QR_CODE] =>
[RST_CREATED_DATE] => 1394536725
[RST_MODIFIED_DATE] => 1394536725
[RST_STATUS] => 1
)
[Category] => Array
(
[CAT_ID] => 2
[CAT_NAME] => Vegetarian
[CAT_CREATED_DATE] => 1375175962
[CAT_MODIFIED_DATE] => 1375175962
[CAT_STATUS] => 1
)
My Model Code:
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'RST_CAT_ID',
'conditions' => array('Category.CAT_ID IN ( Restaurant.RST_CAT_ID)')
)
);
Real Query:
SELECT
`Restaurant`.`RST_ID`, `Restaurant`.`RST_NAME`, `Restaurant`.`RST_IMAGE`,
`Restaurant`.`RST_CAT_ID`, `Restaurant`.`RST_CT_ID`, `Restaurant`.`RST_IS_TOP`,
`Restaurant`.`RST_QR_CODE`, `Restaurant`.`RST_CREATED_DATE`,
`Restaurant`.`RST_MODIFIED_DATE`, `Restaurant`.`RST_STATUS`,
`Category`.`CAT_ID`, `Category`.`CAT_NAME`, `Category`.`CAT_CREATED_DATE`,
`Category`.`CAT_MODIFIED_DATE`, `Category`.`CAT_STATUS`, `City`.`CT_ID`,
`City`.`CT_NAME`, `City`.`CT_CREATED_DATE`, `City`.`CT_MODIFIED_DATE`,
`City`.`CT_STATUS`
FROM `dailybit_dailybites`.`restaurant` AS `Restaurant`
LEFT JOIN `dailybit_dailybites`.`category` AS `Category`
ON (`Restaurant`.`RST_CAT_ID` = `Category`.`CAT_ID`
AND `Category`.`CAT_ID` IN ( `Restaurant`.`RST_CAT_ID`))
LEFT JOIN `dailybit_dailybites`.`city` AS `City`
ON (`Restaurant`.`RST_CT_ID` = `City`.`CT_ID`)
WHERE 1 = 1
So what’s the solution here?
It's giving me just one category data that for first id only.
First have a look at this question: MySQL search in comma list
As you can see the belongsTo query is just generating a join on the single id, CakePHP by default doesn't respect this special case. You will have to alter your query and pass all the ids manually, but your DB design is bad and it doesn't follow the CakePHP conventions at all.
How do you prevent duplicates (which would waste space)
How do you remove a given value (Requires custom function, leading to possibility of errors?
How do you respond to performance issues as the size of my tables increase?
Instead of changing the query you should change this awkward DB design. You want to use HABTM here and a join table: Restaurant hasAndBelongsToMany Categoryy.
restaurants <-> restaurants_categories <-> categories
If you insist on using this bad DB design you'll have to use bindModel() and set the conditions manually:
'conditions' => array('FIND_IN_SET (Category.CAT_ID, ' . $listOfIds. ')')
I haven't tested this, try it yourself, see FIND_IN_SET() vs IN()
You'll have to have another method that gets you all the ids you want here before. Like I said, this is ineffectice and bad design.
You have to set your foreign Key false and find_in_set condition
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => false,
'conditions' => array('FIND_IN_SET(Category.CAT_ID,Restaurant.RST_CAT_ID)')
)
);
// you can pass an array at the place of 'Restaurant.RST_CAT_ID'

How to show data from different related models in cakephp?

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.

CakePHP edit multiple records at once

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

CakePHP and HABTM Model Limit Error

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

Categories