Cakephp Save with a table where the primary key is not 'id' - php

I have an existing web application that I am converting to use CakePHP.
The problem is that the primary keys for most of the tables are in this format "${table_name}_id" (story_id) instead of the CakePHP way of 'id'
When ever I try to update some of the fields for a row in the story table, the Save() function will return false. Is there any way of getting a more detailed error report from the Save() function. ?
When I set Configure::write('debug', 2); in core.php and check the SQL statements I do not see any UPDATE command, only SELECT statements.
I tried to edit the controller adding the following line to manually set the id field for the controller but it did not help.
$this->Story->id = $this->data['Story']['story_id'] ;
I'm running out of ideas. Any suggestions?
I have included the source code that I am using below
Story controller:
function admin_edit($id = null)
{
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Invalid '. Configure::read('Site.media') , true));
$this->redirect(array('action'=>'index'));
}
$this->layout = 'admin';
if (!empty($this->data)) {
if ($this->Story->save($this->data)) {
$this->Session->setFlash(__('The '. Configure::read('Site.media') .' has been saved', true));
} else {
$this->Session->setFlash(__('The '. Configure::read('Site.media') .' could not be saved. Please, try again.', true));
}
}
$this->data = $this->Story->read(null, $id );
}
Story model:
class Story extends AppModel {
var $name = 'Story';
var $primaryKey = 'story_id';
var $validate = array(
'author_id' => array('numeric'),
'title' => array('notempty'),
'story' => array('notempty'),
'genra' => array('notempty'),
'form' => array('notempty'),
'wordcount' => array('Please enter a number between 1 and 1000' => array(
'rule' => array('range', 1, 1001),
'message' => 'Please enter a number between 1 and 1000' ),
'Required' => array( 'rule' => 'numeric', 'required' => true )
)
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'Author' => array(
'className' => 'Author',
'foreignKey' => 'author_id'
)
);
var $hasMany = array(
'UserNote' => array(
'className' => 'UserNote',
'foreignKey' => 'story_id',
'dependent' => false,
'conditions' => 'UserNote.notes != ""'
)
);
}
Story view:
echo $form->create('Story', array('action' => 'edit' ) );
echo $form->input('story_id',array('type'=>'hidden') );
echo $form->input('title');
echo $form->input('story');
echo $form->input('bio' );
echo $form->end('Update story details');?>
Story table
CREATE TABLE IF NOT EXISTS `stories` (
`story_id` int(11) NOT NULL AUTO_INCREMENT,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`closed` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`author_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`story` text NOT NULL,
`genra` varchar(255) NOT NULL,
`form` varchar(128) DEFAULT NULL,
`wordcount` varchar(255) NOT NULL,
`terms` varchar(255) NOT NULL DEFAULT '0',
`status` varchar(255) NOT NULL DEFAULT 'slush',
`published` date NOT NULL,
`payment` varchar(255) NOT NULL DEFAULT 'none',
`paypal_address` varchar(255) NOT NULL,
`resubmission` tinyint(1) NOT NULL DEFAULT '0',
`bio` text NOT NULL,
`password` varchar(255) NOT NULL DEFAULT 'yyggrrdd',
`comments` text NOT NULL,
PRIMARY KEY (`story_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=10905 ;

You should manually override the primary key field in the model (which is the right place to do this - the name of a primary key field is an attribute of the model, and not something that should be 'fudged' around in the controller.)
class Example extends AppModel { var $primaryKey = 'example_id'; // example_id is the field name in the database}
The above code is from http://book.cakephp.org/view/437/primaryKey
While the suggestion to turn off validation will work, it's not the right way to go about it.
Lastly, if you're setting model variables within a controller, you use $this->Model->set('attributeName',value) rather than $this->Model->attributeName

It looks like the story controller was validating the data, and the data was invalid.
Adding the following line to the controller will stop the validation of the data.
$this->Story->validate = array(); // Stop valadation on the story.
I found this solution on this page
15 essential CakePHP tips

Related

Phil Sturgeon's REST server, Codeigniter3, error messages no return on PUT

I am using Phil Sturgeon's REST server, CI3 and POSTMAN for debugging. I send a PUT with below info, however, I am not receiving the error messages expected.
Here is my form_validation.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$config = array(
'student_put' => array(
array('field' => 'email_address', 'label' => 'email_address', 'rules' => 'trim|required|valid_email'),
array('field' => 'password', 'label' => 'password', 'rules' => 'trim|required|min_length[8]|max_length[16]'),
array('field' => 'first_name', 'label' => 'first_name', 'rules' => 'trim|required|max_length[50]'),
array('field' => 'last_name', 'label' => 'last_name', 'rules' => 'trim|required|max_length[50]'),
array('field' => 'phone_number', 'label' => 'phone_number', 'rules' => 'trim|required|alpha_dash'),
)
);
?>
Here is my method in my controller Api.php:
function student_put(){
$this->form_validation->set_data($this->put());
// these are the rules set in config/form_validation.php
if ($this->form_validation->run('student_put') != FALSE) {
die('good data');
} else {
$this->response(
array(
'status'=> 'failure',
'message'=> $this->form_validation->get_errors_as_array(),
),
REST_Controller::HTTP_BAD_REQUEST
);
}
}
This is in my libraries folder as MY_Form_validation.php:
<?php
class MY_Form_validation extends CI_Form_validation {
function __construct($rules = array()) {
parent::__construct($rules);
$this->ci =& get_instance();
}
public function get_errors_as_array() {
return $this->_error_array;
}
public function get_config_rules() {
return $this->_config_rules;
}
public function get_field_names($form) {
$field_names = array();
$rules = $this->get_config_rules();
$rules = $rules[$form];
foreach ($rules as $index=> $info) {
$field_names[] = $info['field'];
}
return $field_names;
}
}
When I put following in POSTMAN:
X-API-KEY 123456
first_name test
email_address abc
This is the result I get:
{
"status": "failure",
"message": []
}
But I should be getting the validation errors.
As debugging steps, I have confirmed:
- no auth errors
- the form_validation.php is being read
- if I change:
'message'=> $this->form_validation->get_errors_as_array(),
to
'message'=> 'test',
the postman returns:
{
"status": "failure",
"message": "test"
}
Any help very much appreciated.
you must read this link,
http://code.tutsplus.com/tutorials/working-with-restful-services-in-codeigniter-2--net-8814
if you use apikey, you must set
$config['rest_auth'] = 'basic'
$config['rest_enable_keys'] = TRUE;
also make a table in database for storing api key
CREATE TABLE `keys` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`key` VARCHAR(40) NOT NULL,
`level` INT(2) NOT NULL,
`ignore_limits` TINYINT(1) NOT NULL DEFAULT '0',
`is_private_key` TINYINT(1) NOT NULL DEFAULT '0',
`ip_addresses` TEXT NULL DEFAULT NULL,
`date_created` INT(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into that database minimum 1 row, the important column only key, it is the apikey
the apikey must contains 40 digits alphanumeric for security reasons
and again, you must read documentation, and the rest.php in application/config
$config['rest_valid_logins'] = ['admin' => '1234'];
that login is set by default, so you must insert that login in your header of client request, etc
http_user 'admin'
http_pass '1234'
X-API-KEY '123456'
first_name test
email_address abc
if that header not work, try this
http_user 'admin'
http_pass '1234'
api_name 'X-API-KEY'
api_key '123456'
first_name test
email_address abc
if you have try request like this before with your
$config['rest_auth'] = FALSE
actually you not yet securing your api webservice
I was placing the PUT variables in the Headers tab within POSTman.
Only the X-API-KEY belongs in the request header. The rest of the data (e.g. email_address, first_name, etc) should be passed in the request body (e.g. from within the Body tab of POSTman).
All works correctly now.

CakePHP AJAX City / State dropdown population

I am working on a CakePHP application that has a City / State dropdown box (to prevent user error on input). We have a controller / database table called "Cities" that contains each city in the US along with State, Zip, Lat and Lng. I've included the creation code for the table below for reference.
CREATE TABLE IF NOT EXISTS `cities` (
`zip` int(5) DEFAULT NULL,
`state` varchar(2) DEFAULT NULL,
`city` varchar(16) DEFAULT NULL,
`lat` decimal(8,6) DEFAULT NULL,
`lng` decimal(10,6) DEFAULT NULL
)
For my Cities controller (controllers/CitiesController.php), I have two custom find functions to help populate the dropdown (via JSON)
class CitiesController extends AppController {
public function getstates() {
$this->set('states', $this->City->find('all', array(
'fields' => array('DISTINCT City.state'),
'order' => 'City.state ASC',
'group' => 'City.state',
'recursive' => 0
)));
$this->set('_serialize', array('states'));
}
public function getCitiesInState($state) {
$this->set('cities', $this->City->find('all', array(
'fields' => array('City.city', 'City.zip', 'City.lat', 'City.lng'),
'order' => 'City.city ASC',
'group' => 'City.city',
'conditions' => array('City.state = ' => strtoupper($state)),
'recursive' => 0
)));
$this->set('_serialize', array('cities'));
}
}
Now this is where I am having a problem.
I am trying to implement this on another models "Add" method (views/Facilities/add.ctp). I'm not sure the best way to call the data for the dropdown. I know I will want to make a jQuery onchange event tied to the States dropdown, as suggested in https://stackoverflow.com/a/1872282/722617
The models are associated, the "Facilities" model has a foreign key of "city_id" that is tied to the id of the city from the Cities model.
The current code just lists all the cities in the database, and looks as follows:
<div class="form-group">
<?php echo $this->Form->input('city_id', array('class' => 'form-control', 'placeholder' => 'City Id'));?>
</div>
Any suggestions for properly implementing this code?
Thank you!

CakePHP save empty model

I have 2 types of users: employees and customers and I need to distinguish between them so I have created 2 separate tables for them. Then I chose CakePHP as my framework and then I wanted to follow Simple Authentication tutorial where is one table for Users. So I have decided to create tables like this:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`username` varchar(36) COLLATE utf8_czech_ci NOT NULL,
`password` varchar(36) COLLATE utf8_czech_ci NOT NULL,
`role` varchar(30) COLLATE utf8_czech_ci NOT NULL,
`name` varchar(30) COLLATE utf8_czech_ci NOT NULL,
`surname` varchar(40) COLLATE utf8_czech_ci NOT NULL,
`phone` varchar(16) COLLATE utf8_czech_ci NOT NULL,
`email` varchar(40) COLLATE utf8_czech_ci NOT NULL,
`employee_id` int(20) DEFAULT NULL,
`customer_id` int(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `EMAIL` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `employee` (
`id` int(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `customer` (
`id` int(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT AUTO_INCREMENT=1 ;
My models:
Employee
public $hasOne = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'employee_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
User:
public $belongsTo = array(
'Employee' => array(
'className' => 'Employee',
'foreignKey' => 'employee_id',
'conditions' => '',
'fields' => '',
'order' => ''
);
Add User function:
public function add() {
if ($this->request->is('post')) {
$this->User->create();
$roles = array('admin', 'employee');
if (in_array($this->request->data['User']['role'], $roles)) {
if ($this->User->Employee->save($this->request->data))
$this->request->data['User']['employee_id'] = $this->User->Employee->getLastInsertId();
else {
$this->Session->setFlash(__('Employee could not be saved.'));
return;
}
}
else {
$this->User->Customer->save($this->request->data);
$this->request->data['User']['customer_id'] = $this->User->Customer->getLastInsertId();
$this->User->Customer->create();
}
if (!$this->User->save($this->request->data)) {
$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
}
else {
$this->Session->setFlash(__('The user has been saved.'));
}
//return $this->redirect(array('action' => 'index'));
}
$employees = $this->User->Employee->find('list');
$customers = $this->User->Customer->find('list');
$this->set(compact('employees', 'customers'));
}
I have a feeling that this conceptual model is not right because Employee and Customer tables contain only primary keys.
Also ($this->User->Employee->save($this->request->data) returns false. Is there a problem that CakePHP is not able to save empty model?
Or do you have any better idea how to model these tables?
Thanks.
If you intend to have information specific to an employee type user and a customer type user, the direction you're going in is fine, and you'd add those future fields to the customer and employee tables. If all you need to do is distinguish between an employee type user and a customer type user, then all you need is a field in your user table to distinguish type, such as
is_employee tinyint(1) default 0,

CakePHP linking a hasMany relationship with a different index

I have a table of securities like so:
CREATE TABLE IF NOT EXISTS `securities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticker` varchar(36) NOT NULL,
`name` varchar(180) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ticker` (`ticker`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=585 ;
I.e. the primary key is id whilst there is another unique index ticker.
The ticker index refers to my other table, secuity_prices which has this
CREATE TABLE IF NOT EXISTS `security_prices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`price_date` date NOT NULL,
`ticker` varchar(36) NOT NULL,
`price` decimal(10,6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=88340 ;
I want to define a hasMany relationship between them such that security hasMany securityPrice [securityPrice belongsTo security].
The problem I am having is that Cake is using the primary key of security to link to the security_prices table instead of the ticker field. How can I get the join to be made via the ticker?
Here are my relationships:
//Security
public $hasMany = array(
'SecurityPrice' => array(
'className' => 'SecurityPrice',
'foreignKey' => 'ticker',
)
);
//SecurityPrice
public $belongsTo = array(
'Security' =>
array(
'className' => 'Security',
'foreignKey' => 'ticker',
)
);
You can't use $hasMany to do this, because those associations require that you follow Cake's naming conventions for the primary key. You are trying to join two tables via non-primary key columns. That can be done, but not via Cake's automatic associations.
You need to add the join conditions when performing a find operation or pagination operation.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#joining-tables
$options['joins'] = array(
array('table' => 'security_prices',
'alias' => 'SecurityPrice',
'type' => 'LEFT',
'conditions' => array(
'Security.ticker = SecurityPrice.ticker',
)
)
);
$Security->find('all', $options);
If you have to do this often, then you should create a custom find type.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types
class Security extends AppModel {
public $findMethods = array('ticker' => true);
protected function _findTicker($state, $query, $results = array()) {
if ($state === 'before') {
$query['joins'][] = array(
array('table' => 'security_prices',
'alias' => 'SecurityPrice',
'type' => 'LEFT',
'conditions' => array(
'Security.ticker = SecurityPrice.ticker',
)
)
);
return $query;
}
return $results;
}
}
Then later it's easy to find with the join.
$Security->find('ticker',.....);

CakePHP HABTM model problem

I have a problem when using CakePHP HABTM.
I have the following models.
class Repositorio extends AppModel{
var $name="Repositorio";
var $hasAndBelongsToMany = array(
'Sesion' =>
array(
'joinTable' => 'sesions_repositorios',
'dependent' => true
)
);
var $order=array('Repositorio.name'=>'ASC');
}
class Sesion extends AppModel{
var $name="Sesion";
var $belongsTo=array(
'SesionsEstado',
'Asignatura',
'User'
);
var $hasAndBelongsToMany = array('Repositorio'=>
array(
'joinTable'=>'sesions_repositorios',
'dependent' => true
)
);
var $order=array('Sesion.ffin'=>'ASC');
}
And the following database tables.
CREATE TABLE sesions (
id INT(11) NOT NULL AUTO_INCREMENT,
user_id INT(11) NOT NULL,
sesions_estado_id INT(11) NOT NULL,
asignatura_id INT(11) NOT NULL,
name VARCHAR(100) NOT NULL,
finicio DATETIME NOT NULL,
ffin DATETIME NOT NULL,
created DATETIME NOT NULL,
modified DATETIME NOT NULL,
PRIMARY KEY(id),
INDEX sesions_FKIndex1(sesions_estado_id),
INDEX sesions_FKIndex2(asignatura_id),
INDEX sesions_FKIndex3(user_id)
);
CREATE TABLE repositorios (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
created DATETIME NOT NULL,
modified DATETIME NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE sesions_repositorios (
id INT(11) NOT NULL AUTO_INCREMENT,
sesion_id INT(11) NOT NULL,
repositorio_id INT(11) NOT NULL,
PRIMARY KEY(id),
INDEX sesions_repositorios_FKIndex1(sesion_id),
INDEX sesions_repositorios_FKIndex2(repositorio_id)
);
When I save the data in a repository all work properly, that is, it performs an INSERT on the table "repositorios" and performs the corresponding INSERT on table "sesions_repositorios.
My problem comes when I get a list of repositories for a particular user. The code for this would be.
class RepositoriosController extends AppController{
...
$r=$this->Repositorio->Sesion->find('all', array('conditions'=>array('user_id'=>$this->Session->read('Auth.User.id'))));
var_dump($r);
...
}
The $r variable does not contain the filtered data for user_id, why?, what am I doing wrong?
I have not set foreign key's, could that be the problem?
Thanks for the help.
I believe that you need to add in something like 'recursive' => 1 or whatever depth you want it to search your linked models into your query.
$r=$this->Repositorio->Sesion->find('all', array('conditions'=>array('user_id'=>$this->Session->read('Auth.User.id')),'recursive'=>1));
I'm sorry, the code is actually quite correct. Was failing by other issues.
Thanks for everything.
Greetings!

Categories