Cake php multimodel form post parameters - cakephp

I'm a cakephp newbie, and I was ordered to use the 1.3 version.
I can't understand (and both guides and api docs don't tell it) how I could create an HABTM association in a POST request.
I'm trying to create a wine, that could be made of many vines. For example I'm creating a "soave" whine, that is made of "garganega" and "chardonnay" vines.
How should the POST params should be?
Given theses models
class Wine extends AppModel{
var $hasAndBelongsToMany = array(
'Vine' => array(
'className' => 'Vine',
'joinTable' => 'wine_vines',
'foreignKey' => 'wine_id',
'associationForeignKey' => 'vine_id',
'with' => 'WineVine',
),
);
}
class Vine extends AppModel{
var $hasAndBelongsToMany = array(
'Wine' => array(
'className' => 'Wine',
'joinTable' => 'wine_vines',
'foreignKey' => 'vine_id',
'associationForeignKey' => 'wine_id',
'with' => 'WineVine',
),
);
}
class WineVine extends AppModel{
var $name = "WineVine";
public $belongsTo = array("Wine", "Vine");
}
I tried a POST like this:
Array
(
[Wine] => Array
(
[denomination] => Soave DOP
[fantasy_name] =>
[kind] => White
)
[Vine] => Array
(
[0] => Array
(
[name] => garganega
)
[2] => Array
(
[name] => chardonnay
)
)
)
but it does not perform any inserts in vine table, only in wine.
Here's the log:
2 INSERT INTO `wines` (`denomination`, `fantasy_name`, `kind`, `modified`, `created`) VALUES ('', '', '', '2013-10-25 17:27:14', '2013-10-25 17:27:14') 1 55
3 SELECT LAST_INSERT_ID() AS insertID 1 1 1
4 SELECT `WineVine`.`vine_id` FROM `wine_vines` AS `WineVine` WHERE `WineVine`.`wine_id` = 2 0 0 1
5 SELECT `Vine`.`id`, `Vine`.`name`, `Vine`.`created`, `Vine`.`modified` FROM `vines` AS `Vine` WHERE 1 = 1 5 5 0
6 SELECT `Wine`.`id`, `Wine`.`denomination`, `Wine`.`fantasy_name`, `Wine`.`kind`, `Wine`.`created`, `Wine`.`modified`, `Wine`.`producer_id`, `WineVine`.`id`, `WineVine`.`wine_id`, `WineVine`.`vine_id`, `WineVine`.`created`, `WineVine`.`modified` FROM `wines` AS `Wine` JOIN `wine_vines` AS `WineVine` ON (`WineVine`.`vine_id` IN (1, 2, 3, 4, 5) AND `WineVine`.`wine_id` = `Wine`.`id`)

after saving the wine, try injecting its id into the data array
$this->data['Wine']['id'] = $this->Wine->id;
and then call an overloaded Model::saveAssociated() that will save all vines and update the join table by itself.
this overloaded method is described at:
http://bakery.cakephp.org/articles/ccadere/2013/04/19/save_habtm_data_in_a_single_simple_format
edit: sorry, that's for cake 2.x
i just realized 1.3 has no saveAssociated method
edit 2: but it does work in cake 1.3 if you change the last line of the saveAssociated method to
return parent::saveAll($data, $options);

Related

CakePHP Many To Many With Conditions

I'm newbie on CakePHP, and now I'm stuck on many to many situation
ok, i have 3 Table :
questions
with fields (id, question)
question_product
with fields (id, question_id, product_id, question_number, is_enabled)
products
with fields (id, name, code, is_enabled)
so when i want to select questions with specific field, i don't know how to fix it
for now, my code is like this :
Question.php (Model)
class Question extends AppModel {
public $hasAndBelongsToMany = array (
'Product' => array (
'joinTable' => 'question_product',
'foreignKey' => 'question_id',
'associationForeignKey' => 'product_id',
'unique' => 'keepExisting',
'order' => 'question_number',
'fields' => array (
'QuestionProduct.question_number',
'Product.id',
'Product.name'
),
'conditions' => array (
'QuestionProduct.is_enabled' => 1,
)
)
);
}
QuestionsController.php (Controller)
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->Question->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
)
));
$this->set($questions);
}
method loadQuestions have one parameter to select with specified product
if i using sql query, it will be like this
select all from Question with condition Product.product_id=4, sorted by QuestionProduct.question_number ascending
select questions.*
from questions
join question_product on questions.id=question_product.question_id
join products on products.id=question_product.product_id
where products.id=4
order by question_product.question_number;
any answer will be appreciated :)
Thanks !
Any time you use a many-many (HABTM) relation with any other field that requires conditions, it is no longer many-many as far as Cake is concerned. You want the HasManyThrough relationship
Instead of using hasAndBelongsToMany relation, use two belongsTO relation from question_product to questions and another time from question_product to products.
question_product belognsTo questions
question_product belongsTo products
NOTE:you should change the table name from question_product to question_products as cakePHP convention
in your model QuestionProduct model :
<?php
// declares a package for a class
App::uses('AppModel', 'Model');
class QuestionProduct extends AppModel {
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
),
'Question' => array(
'className' => 'Question',
'foreignKey' => 'question_id',
),
);
then in your Controller :
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->QuestionProduct->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
),
'contain' => array('Product','Question')
));
$this->set($questions);
}
It should make exactly the query you want, and I don't think it has any other way to produce that query.

Second Level Expansion?

First of all, sorry if this sounds too simple. I'm new to cakePHP by the way.
My objective is to expand the team_id in season controller where it linked thru standings table.
My current model rule are:
Each season has many standings
Each standings denotes a team.
Basically is a many to many relation between season and team = standings.
Here's my SeasonController:
class SeasonsController extends AppController
{
public $helpers = array('Html', 'Form');
public function view($id)
{
$this->Season->id = $id;
$this->set('season', $this->Season->read());
}
}
When I browse a single season thru /cakephp/seasons/view/1, the $season array shows:
Array
(
[Season] => Array
(
[id] => 1
[name] => English Premier League 2013/2014
[start] => 1350379014
[end] => 0
)
[Standing] => Array
(
[0] => Array
(
[id] => 1
[team_id] => 1
[win] => 1
[lose] => 0
[draw] => 1
[GF] => 5
[GA] => 4
[season_id] => 1
[rank] => 1
)
[1] => Array
(
[id] => 2
[team_id] => 2
[win] => 0
[lose] => 1
[draw] => 1
[GF] => 4
[GA] => 5
[season_id] => 1
[rank] => 2
)
)
)
The result only shows team_id, but how can I get further expansion for team_id?
I have put below in my Team's model, but still doesn't auto linked. Below are all my models:
class Team extends AppModel
{
public $name = 'Team';
public $hasMany = array(
'Standing' => array(
'className' => 'Standing'
));
}
class Season extends AppModel
{
public $name = 'Season';
public $hasMany = array(
'Standing' => array(
'className' => 'Standing'
));
}
class Standing extends AppModel
{
public $name = 'Standing';
public $belongsTo = array(
'Team' => array(
'className' => 'Team',
'foreignKey' => 'team_id',
),
'Season' => array(
'className' => 'Season',
'foreignKey' => 'season_id',
),
);
}
TLDR:
Use CakePHP's [Containable] behavior.
Explanation & Code example:
"Containable" lets you "contain" any associated data you'd like. Instead of "recursive", which blindly pulls entire levels of data (and can often cause memory errors), "Containable" allows you to specify EXACTLY what data you want to retrieve even down to the field(s) if you'd like.
Basically, you set your model to public $actsAs = array('Containable'); (I suggest doing that in your AppModel so Containable is available for all models). Then, you pass a "contain" array/nested array of the associated models you want to retrieve (see below example).
After reading about it and following the instructions, your code should look similar to this:
public function view($id = null)
{
if(empty($id)) $this->redirect('/');
$season = $this->Season->find('first', array(
'conditions' => array(
'Season.id' => $id
),
'contain' => array(
'Standing' => array(
'Team'
)
)
);
$this->set(compact('season'));
}
Once you get more comfortable with CakePHP, you should really move functions like this into the model instead of having them in the controller (search for "CakePHP Fat Models Skinny Controllers"), but for now, just get it working. :)
Use :
$this->Season->id = $id;
$this->Season->recursive = 2;
$this->set('season', $this->Season->read());

CakePHP: SaveAll() and hasMany data replace problem

I made one big form with many associations hasMany/HABTM. All is workin great on creating (!). When updating all is working nice too, but in tables where association is hasMany, data is not updated or replaced, but just inserted. This brings many rows with trash data. How can I make saveAll() do the update/replace in hasMany fields:
Model:
class MainModel extends AppModel {
var $hasAndBelongsToMany = array(
'HABTMModel1',
...
'HABTMModeln',
);
var $hasMany = array(
'Model1' => array(
'dependent' => true
),
...
'Modeln' => array(
'dependent' => true
),
);
}
One of the problematic hasMany models look like:
class Model1 extends AppModel {
var $belongsTo = array(
'MainModel'
);
}
And his table have:
id <- Primary key, auto increment, int (11)
main_model_id <- Foreign_key int (11)
name <- text field, string
The $this->data is looking like:
array(
[MainModel] => array(
'id' => 123
*** aditional data named identicaly to table fields (working great)***
),
[Model1] => array(
[0] => array(
[name] => Test1
),
[2] => array(
[name] => Test2
),
),
*** all other models ***
);
Model1 table results after first creating and after updating:
id main_model_id name
--------------------------------------------------------------
11 306 Test1
12 306 Test2
13 306 Test1 (Thease are dublicates)
14 306 Test2 (Thease are dublicates)
What can I do to update/replace data in hasMany and not insert new values un edit using saveAll ?
Thank you.
In the view, just put hidden input for all the Model1, Model2 ids
echo $form->create('MainModel');
echo $form->hidden('Model1.0.id');
// more stuffs...
echo $form->end('Save');
You probably specify the 'fields' for Model1, Model2 to have only 'name'. That's why $this->data looks like that. So just add 'id' to that.
So I made not so beautiful, but working:
// If edit, then delete data from hasMany tables based on main_model_id
if(isset($this->data['MainModel']['id']) && !empty($this->data['MainModel']['id'])) {
$conditions = array('main_model_id' => $this->data['MainModel']['id']);
// Delete Model1
$this->MainModel->Model1->deleteAll($conditions, false);
...
all other models
...
}
$this->MainModel->saveAll($this->data);
You need to set the id field for your hasMany model too e.g.
array(
[MainModel] => array(
[id] => 123
*** aditional data named identicaly to table fields (working great)*** ),
[Model1] => array(
[0] => array(
[id] => 1,
[name] => Test1
),
[2] => array(
[id] => 2
[name] => Test2
), ), *** all other models *** );

CakePHP - Paginating an Array

Cake handles pagination of a model with a simple $this->paginate(), but what should I use if I want to paginate a array of values?
The Scenario is like this:
$this->set('sitepages', $this->paginate());
This code in my index() returns an array like
Array
(
[0] => Array
(
[Sitepage] => Array
(
[id] => 13
[name] => Home
[urlslug] => home
[parent_id] => 1
[page_title] => Welcome to KIAMS, Pune
[order] => 1
)
)
[1] => Array
(
[Sitepage] => Array
(
[id] => 26
[name] => About Us
[urlslug] => aboutus
[parent_id] => 1
[page_title] =>
[order] => 2
)
)
[2] => Array
(
[Sitepage] => Array
(
[id] => 27
[name] => Overview of KIAMS
[urlslug] => aboutus/overview
[parent_id] => 26
[page_title] =>
[order] => 2
)
)
I retrieved the same data using $this->Sitepage->find('all') and then performed some manipulations as required and form a array which is very similar to the above one, but the ordering gets changed. I want to paginate this new array and pass it to the view. I tried
$this->set('sitepages',$this->paginate($newarray))
But the data is not getting paginated. Can some one please help with paginating the $newarray in CakePHP?
To paginate in CakePHP you need to pass select conditions to the paginate() call.
Other data manipulation should be done in afterFind(), in your model file.
If you don't need these changes to be done in every single retrieval, you might as well consider creating a new model file pointing to the very same table as the current one, and adding an afterFind() method to that new file.
I've just dealt with this same problem...
I found the only way is to use the paginate() function to handle all the logic, rather than passing it a custom array. However, this isn't as bad as it seems:
$this->paginate = array(
'limit' => 2,
'order' => array(
'Report.name' => 'asc'
),
'conditions' => array(
'Account.id' => $this->foo()
),
);
$reports = $this->paginate();
In this example, I'm paginating Reports - but some Reports will not be included, depending on which Account they belong to (Account has some relationship with Report, hasmany, etc.).
By writing $paginate inside your action, you can use a custom callback for the conditions array. So function foo() can be written in your controller (or shoved to model) and returns an array of valid Account IDs.
I found I could easily rewrite my custom logic in this function - keeping both me and the Cake paginator happy.
Hope that helps!
I'm using cakephp version 1.3 and it seems this one is working:
at controller:
$result = $this->Paginate('Event');
$results = $this->Task->humanizeEvent($result);
$this->set('events', $results);
and it seems to display as a normal paginated array, at the view (setup your pagination in view as normal).
The humanizeEvent function just edits a field on the results array, to make it a sentence based on other fields inside the array, and it seems to work properly.
$this->paginate($newarray) is the wrong way to paginate. The first parameter cannot be an array. It must be a model name. You may want to study pagination setup from the manual. This will order alphabetically:
var $paginate = array(
'limit' => 25,
'order' => array(
'Sitepage.name' => 'asc'
)
);
$this->set('sitepages', $this->paginate('Sitepage'));
I create a component checking the paginator code..
Is not the best thing, but It work for me.....
Controller
$slicedArray = array_slice($fullArray,($page - 1) * $this->PaginatorArray->limit ,$this->PaginatorArray->limit)
$this->params['paging'] = $this->PaginatorArray->getParamsPaging('MyModel', $page, $total,count($slicedArray));
$this->helpers[] = 'Paginator';
Component
<?php
/* SVN FILE: $Id$ */
/**
* Pagination Array Component class file.
* #subpackage cake.cake.libs.view.helpers
*/
class PaginatorArrayComponent {
var $limit = 40;
var $step = 1;
function startup( & $controller){
$this->controller = & $controller;
}
function getParamsPaging($model, $page, $total, $current){
$pageCount = ceil($total / $this->limit);
$prevPage = '';
$nextPage = '';
if($page > 1)
$prevPage = $page - 1;
if($page + 1 <= $pageCount)
$nextPage = $page + 1;
return array(
$model => array(
'page' => $page,
'current' => $current,
'count' => $total,
'prevPage' => $prevPage,
'nextPage' => $nextPage,
'pageCount' => $pageCount,
'defaults' => array(
'limit' => $this->limit,
'step' => $this->step,
'order' => array(),
'conditions' => array(),
),
'options' => array(
'page' => $page,
'limit' => $this->limit,
'order' => array(),
'conditions' => array(),
)
)
);
}
}
?>
There was a bug in find("count") and it returned incorrect count if the query resulted in records for only in group. This has been fixed click here

saveall with hasOne saves blank foreign key

I have th e following model relationships:
Enquiry:
var $hasOne = array(
'SeminarAttendence' => array(
'className' => 'SeminarAttendence'
)
);
SeminarAttendence:
var $belongsTo = array(
'Enquiry' => array(
'className' => 'Enquiry',
'foreign_key' => 'enquiry_id',
)
);
my post data looks like this:
[Enquiry] => Array
(
[first_name] => joe
[last_name] => soap
[email_address] =>
[tel_home] =>
[tel_work] =>
[tel_cell] =>
)
[SeminarAttendence] => Array
(
[branch_id] => 178 // this has no table relation it's for a web service
)
I saveAll this in a controller:
$this->Enquiry->saveAll($this->data, array('validate' => 'first', 'atomic' => false
when I am done I get result like this in the SeminarAttendence
id branch_id enquiry_id
1 4 0
2 4 0
3 3 0
4 1 0
It worked fine on php5 yesterday, now when i ported it to our dev server (php4 ) it doesnt work?
It's not cakephp problem. because cakephp is made for php4, and works properly on php5. maybe something wrong with datebase, or config

Resources