CakePHP SaveAssociated / SaveAll ignoring fields - php

I am having an issue where CakePHP is ignoring saving a field I specify and instead creating extra records. I am using CakePHP 2.3.6.
My table looks like this:
CREATE TABLE `events_guests` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`guest_id` int(11) NOT NULL,
`event_id` int(11) NOT NULL,
`promoter_id` int(11) DEFAULT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`attended` varchar(15) NOT NULL DEFAULT 'No',
PRIMARY KEY (`id`)
)
Heres the code
public function addGuest($event_id, $promoter_id = null) {
if ($this->request->is('post')) {
$this->Guest->create();
$event_data = array('event_id' => $event_id);
$data = $this->request->data;
$data['Event'] = $event_data;
if($promoter_id) {
$data['Event']['promoter_id'] = $promoter_id;
}
if ($this->Guest->saveAssociated($data)) {
$this->Session->setFlash(__('The guest has been added to the guestlist'), 'flash/success');
} else {
$this->Session->setFlash(__('The guest could not be saved. Please, try again.'), 'flash/error');
}
Here is my data that I am trying to save:
Array (
[Guest] => Array
(
[first_name] => Joe
[last_name] => Schmoe
)
[Event] => Array
(
[event_id] => 1
[promoter_id] => 2
)
)
My Models follow:
class Guest extends AppModel {
public $hasAndBelongsToMany = array(
'Event' => array(
'className' => 'Event',
'joinTable' => 'events_guests',
'foreignKey' => 'guest_id',
'associationForeignKey' => 'event_id',
'unique' => 'true',
),
'Promoter' => array(
'className' => 'Promoter',
'joinTable' => 'events_guests',
'foreignKey' => 'guest_id',
'associationForeignKey' => 'promoter_id',
'unique' => 'true',
)
);
}
And
class Event extends AppModel {
public $hasAndBelongsToMany = array(
'Guest' => array(
'className' => 'Guest',
'joinTable' => 'events_guests',
'foreignKey' => 'event_id',
'associationForeignKey' => 'guest_id',
'unique' => 'keepExisting',
'order' => 'last_name',
),
'Promoter' => array(
'className' => 'Promoter',
'joinTable' => 'events_promoters',
'foreignKey' => 'event_id',
'associationForeignKey' => 'promoter_id',
'unique' => 'keepExisting',
)
);
}
The results from this are 2 records for EventsGuests, neither with a promoter_id.
One record receives event_id = 1 AS EXPECTED
Other record receives event_id = 2, which is actually the promoter_id
I have tried using a mix of saveAssociated and saveAll
Any help is greatly appreciated! Thank you!

Reference: Cakephp save extra attribute in HABTM relation
For your problem, don't use HABTM, because, HABTM is normally use for connect 2 tables.
So, if you have more than 2 fields, create a model and use it like belongsTo and hasMany relationships.
class Event extends AppModel {
$hasMany = array(
'EventGuest' => array(
'className' => 'EventGuest',
'foreignKey' => 'event_id'
)
);
}
class Guest extends AppModel {
$hasMany = array(
'EventGuest' => array(
'className' => 'EventGuest',
'foreignKey' => 'guest_id'
)
);
}
class Promoter extends AppModel {
$hasMany = array(
'EventGuest' => array(
'className' => 'EventGuest',
'foreignKey' => 'promoter_id'
)
);
}
Now, your relationship model.
class EventGuest extends AppModel {
$belongsTo = array(
'Event' => array(
'className' => 'Event',
'foreignKey' => 'event_id'
),
'Guest' => array(
'className' => 'Guest',
'foreignKey' => 'guest_id'
),
'Promoter' => array(
'className' => 'Promoter',
'foreignKey' => 'promoter_id'
)
);
}
And continue use your code.
public function addGuest($event_id, $promoter_id = null) {
if ($this->request->is('post')) {
$this->Guest->create();
$event_data = array('event_id' => $event_id);
$data = $this->request->data;
$data['Event'] = $event_data;
if($promoter_id) {
// EDITED
$data['Promoter']['promoter_id'] = $promoter_id;
}
if ($this->Guest->saveAssociated($data)) {
$this->Session->setFlash(__('The guest has been added to the guestlist'), 'flash/success');
} else {
$this->Session->setFlash(__('The guest could not be saved. Please, try again.'), 'flash/error');
}
}
}

Related

CakePHP - saving HABTM fails

I have tables that looks like this. Basically WarehousePlace can have many ItemType in it and ItemType can be used by many WarehousePlace. But when I try to save I get an database error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'WarehousePlacesItemType.item_type_id' in 'field list'
SQL Query: SELECT `WarehousePlacesItemType`.`item_type_id` FROM `modules`.`warehouse_places_item_types` AS `WarehousePlacesItemType` WHERE `WarehousePlacesItemType`.`warehouse_place_id` = '22'
But everything is done by conventions and also debugged data is like it should be and I do not see what is wrong.
WarehousesController
public function add() {
$this->load();
if ($this->request->is('post')) {
$data = array(
'WarehousePlace' => array(
'name' => $this->request->data['WarehousePlace']['name'],
),
'ItemType' => array(
'ItemType' => $this->request->data['ItemType']['ItemType']
)
);
$this->WarehousePlace->create();
if ($this->WarehousePlace->saveAll($data, array('validate' => 'first', 'deep' => true))) {
//success
}
if ($this->WarehousePlace->validationErrors) {
//validation
} else {
//error
}
}
}
tables:
warehouse_places_item_types(warehouse_places_id,item_types_id) -> no primary key since CakePHP 2,
warehouse_places(id, name),
item_types(id, name)
Models:
ItemType
public $hasAndBelongsToMany = array(
'WarehousePlace' => array(
'className' => 'WarehousePlace',
'joinTable' => 'warehouse_places_item_types',
'foreignKey' => 'item_type_id',
'associationForeignKey' => 'warehouse_place_id',
'unique' => 'keepExisting',
)
);
WarehousePlace
public $hasAndBelongsToMany = array(
'ItemType' => array(
'className' => 'ItemType',
'joinTable' => 'warehouse_places_item_types',
'foreignKey' => 'warehouse_place_id',
'associationForeignKey' => 'item_type_id',
'unique' => 'keepExisting',
)
);

Creating a User who has-and-belongs-to-many existing Courses with a CakePHP form

I have an app with two associated models: User and Course, which are related by a HABTM association.
There is a registration form where a new user may enter a username and select the courses that they are a part of (from a list of existing courses in the database), only the form only saves the new users - it doesn't save anything to the join table.
The join table (courses_users) has columns course_id and user_id, and the two models look like this:
// User.php
class User extends AppModel {
public $name = 'User';
public $hasAndBelongsToMany = array(
'Courses' => array(
'className' => 'Course',
'joinTable' => 'courses_users',
'foreignKey' => 'user_id',
'associatedForeignKey' => 'course_id'
)
);
}
// Course.php
class Course extends AppModel {
public $name = 'Course';
public $hasAndBelongsToMany = array(
'Users' => array(
'className' => 'User',
'joinTable' => 'courses_users',
'foreignKey' => 'course_id',
'associatedForeignKey' => 'user_id'
)
);
}
In addition, this is the controller action:
// IdentificationController.php
public function register() {
if ($this->request->is('POST')) {
$data = $this->request->data;
$username = $data['User']['username'];
$saved = $this->User->save($data, array('deep' => true));
//debug($data);
if ($saved) {
$this->_set_new_user_session($username);
//$log = $this->User->getDataSource()->getLog(false, false);
//debug($log);
$this->redirect(array('controller' => 'users', 'action' => 'index'));
}
}
// Not redirecting
$courses = $this->Course->find('list', array('Course.name'));
//debug($courses);
$this->set(compact('courses'));
}
And this is the form, sans container divs:
<?php
echo $this->Form->create('User', array(
'inputDefaults' => array(
'label' => false,
'div' => false
),
'url' => '/identification/register'
));
echo $this->Form->input('username', array(
'error' => false,
'autofocus' => true,
'required' => true,
'pattern' => '[a-zA-Z0-9]{3,16}'
));
if ($this->Form->isFieldError('username')) {
echo $this->Form->error('username', null, array('wrap' => 'small', 'class' => 'error'));
}
echo $this->Form->input('Course.Course', array(
'error' => false,
'required' => true
));
if ($this->Form->isFieldError('courses')) {
echo $this->Form->error('course', null, array('wrap' => 'small', 'class' => 'error'));
}
echo $this->Form->button('Register', array(
'div' => false,
'type' => 'submit'
));
echo $this->Form->end();
?>
When I call debug($data), the right data seems to be passed from the form to the controller:
array(
'User' => array(
'username' => 'test63apd'
),
'Course' => array(
'Course' => array(
(int) 0 => '1'
)
)
)
But nothing happens to the join table, and there is no mention of the join table in the DataSource log:
array(
'log' => array(
(int) 0 => array(
'query' => 'BEGIN',
'params' => array(),
'affected' => null,
'numRows' => null,
'took' => null
),
(int) 1 => array(
'query' => 'INSERT INTO `xray2`.`users` (`username`) VALUES ('test06apd')',
'params' => array(),
'affected' => (int) 1,
'numRows' => (int) 1,
'took' => (float) 1
),
(int) 2 => array(
'query' => 'COMMIT',
'params' => array(),
'affected' => (int) 1,
'numRows' => (int) 1,
'took' => (float) 1
)
),
'count' => (int) 3,
'time' => (float) 2
)
Am I missing something really obvious here, or is there some quirk of Cake that I have yet to discover?
You're only telling Cake to save the primary model data. You need to change this line:-
$saved = $this->User->save($data, array('deep' => true));
To:-
$saved = $this->User->saveAssociated($data, array('deep' => true));
saveAssociated() tells Cake to save the current model and its associated data. You also shouldn't need to pass array('deep' => true) as you are only saving data to a directly associated model. So it would be better (and safer) to use:-
$saved = $this->User->saveAssociated($data);
Update
There is an issue with the data being saved as you are not using the alias defined in your association for the data. So when Cake attempts to save the associated data it can't see any. According to your code your User model has and belongs to many Courses (plural) but your save data uses Course (singular). Therefore, your form should be:-
echo $this->Form->input('Courses.Courses', array(
'error' => false,
'required' => true
));
It should be noted that Cake naming conventions use singular forms for model names, so it would be better to use Course in the association rather than Courses. If you change this then your association can be simplified to the following:-
public $hasAndBelongsToMany = array(
'Course'
);
Cake will understand how to handle the join table and foreign keys as they conform to the naming convention. Then you wouldn't need to change your form.

Passing non-form data before save (CakePHP)

Course HasMany Lesson.
I am trying to save a lesson in a Course view, but want to automatically assign the current course_id without them having to select it.
How do I do that?
Here is my Courses view controller:
public function view($id = null) {
if (!$this->Course->exists($id)) {
throw new NotFoundException(__('Invalid course'));
}
$options = array('conditions' => array('Course.' . $this->Course->primaryKey => $id));
$this->set('course', $this->Course->find('first', $options));
//Adding Lesson from Course View
if ($this->request->is('post')) {
$this->Course->Lesson->create();
if ($this->Course->Lesson->save($this->request->data)) {
$this->Session->setFlash(__('The lesson has been saved.'), array ('class' => 'alert alert-success'));
return $this->redirect(array('controller'=> 'lessons', 'action' => 'view', $this->Course->Lesson->id));
} else {
$this->Session->setFlash(__('The lesson could not be saved. Please, try again.'), array ('class' => 'alert alert-danger'));
}
}
$courses = $this->Course->Lesson->Course->find('list');
$this->set(compact('courses'));
}
Currently, it's saving the lesson but not passing the course_id to the new lesson.
Lesson Model:
public $belongsTo = array(
'Course' => array(
'className' => 'Course',
'foreignKey' => 'course_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Course Model:
public $hasMany = array(
'Lesson' => array(
'className' => 'Lesson',
'foreignKey' => 'course_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
This is basically a duplicate of CakePHP: Securely setting a default value on a form.
Check my answer there or read the blog article I've wrote after that answer.

Model not saved to database

CREATE TABLE IF NOT EXISTS `web_subjects` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`web_subject_category_id` int(11) DEFAULT NULL,
`title` varchar(128) DEFAULT NULL,
`type` varchar(128) DEFAULT NULL,
`description` text,
`description_long` text,
`editable` int(1) DEFAULT NULL,
`deletable` int(1) DEFAULT NULL,
`published` int(1) DEFAULT NULL,
`order_number` int(11) DEFAULT NULL,
`created` timestamp NULL DEFAULT NULL,
`modified` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;
Model
class WebSubject extends AppModel
{
public $name = "WebSubject";
public $belongsTo = array("WebSubjectCategory");
public $validate = array(
'title' => array(
'rule' => 'notEmpty',
'message' => "Completati denumirea!"
)
);
public $hasMany = array(
'Image' => array(
'className' => 'WebFile',
'foreignKey' => 'entity_id',
'conditions' => array(
'Image.type' => 'image',
'Image.entity_table_name' => 'web_subjects'
),
'order' => array('Image.order_number ASC', 'Image.id DESC'),
'dependent' => true
),
'Video' => array(
'className' => 'WebFile',
'foreignKey' => 'entity_id',
'conditions' => array(
'Video.type' => 'video',
'Video.entity_table_name' => 'web_subjects'
),
'order' => array('Video.order_number ASC', 'Video.id DESC'),
'dependent' => true
)
);
}
Controller Action
public function admin_page_add(){
if(!empty($this->request->data))
{
$this->WebSubject->create($this->data["WebSubject"]);
$this->WebSubject->type = 'page';
//debug($this->WebSubject);
if($this->WebSubject->save()){
//debug($this->WebSubject);
//die(0);
$this->Session->setFlash("Pagina a fost salvata!", "flash/simpla_success");
$this->redirect('pages');
}
else{
$this->Session->setFlash("Pagina NU a fost salvata!", "flash/simpla_error");
}
}
}
The problem is that the model appeares to be saved and I am redirected as expected, but it doesn't get inserted into the database.
Using debug(Model) I saw that the id that the model is taking is incremented (like it is inserted and then deleted).
I used sql_dump - no trace of INSERT.
And, of course, no validation errors.
What am I missing ?
User this
This is my model
<?php
App::uses('AppModel', 'Model');
/**
* CarModel Model
*
* #property Manufacturer $Manufacturer
* #property Showroom $Showroom
*/
class CarModel extends AppModel {
/**
* Display field
*
* #var string
*/
public $displayField = 'model';
/**
* Validation rules
*
* #var array
*/
public $validate = array(
'manufacturer_id' => array(
'numeric' => array(
'rule' => array('numeric'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'model' => array(
'notempty' => array(
'rule' => array('notempty'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* #var array
*/
public $belongsTo = array(
'Manufacturer' => array(
'className' => 'Manufacturer',
'foreignKey' => 'manufacturer_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
/**
* hasMany associations
*
* #var array
*/
public $hasMany = array(
'Showroom' => array(
'className' => 'Showroom',
'foreignKey' => 'car_model_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'RequestCar' => array(
'className' => 'RequestCar',
'foreignKey' => 'car_model_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
?>
My controller
/**
* admin_add method
*
* #return void
*/
public function admin_add() {
$this->layout = 'admin_layout';
if ($this->request->is('post')) {
$this->CarModel->create();
if ($this->CarModel->save($this->request->data)) {
$this->Session->setFlash(__('The car model has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The car model could not be saved. Please, try again.'));
}
}
$manufacturers = $this->CarModel->Manufacturer->find('list');
$this->set(compact('manufacturers'));
}
$this->WebSubject->save();
saves nothing as there is no data! (Edit : this statement is wrong as said in comments.)
Put:
$this->WebSubject->save($this->request->data);
save() function docs

problem with counterCache of cakePHP

I have in a video sharing website project these models :
class Video extends AppModel {
var $name = 'Video';
var $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'joinTable' => 'videos_tags',
'foreignKey' => 'video_id',
'associationForeignKey' => 'tag_id',
'unique' => true,
)
);
}
class Tag extends AppModel {
var $name = 'Tag';
var $hasAndBelongsToMany = array(
'Video' => array(
'className' => 'Video',
'joinTable' => 'videos_tags',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'video_id',
'unique' => true,
)
);
}
class VideosTag extends AppModel {
var $name = 'VideosTag';
var $belongsTo = array(
'Video' => array(
'className' => 'Video',
'foreignKey' => 'video_id',
),
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
'conditions' => '',
'counterCache' => 'videos_tag_counter'
)
);
}
The counterCache for tags is not working. I don't know why and when I tried to add a beforeSave() callback to videosTag model I found that it doesn't execute when a video get saved (and this video has tags and i find them in the database so how the relation videosTags is saved ? ) !!! can any body explain why this is happening.
Saving a Video with data like this:
array(
'Video' => array(
...
),
'Tag' => array(
'Tag' => array(
...
),
),
);
on the Video model will not trigger a beforeSave callback on the VideosTag model because Cake handles HABTM data without requiring (or even using) the join/with/through model.
There is no built in functionality for what you are trying to achieve, as far as I am aware.
Check out Counter Cache behavior for HABTM relations, might do what you need

Categories