Cakephp 3 create entry, set custom primary field - php

I have a roles table. Looks like this:
CREATE TABLE `roles` (
`role` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`permissions` longtext COLLATE utf8_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `roles`
ADD PRIMARY KEY (`role`),
ADD UNIQUE KEY `role` (`role`);
Now cake is not recognizing it as a "normal" field, so it doesn't give out any input field.
I fixed my view with this:
// src/Template/Admin/Roles/add.ctp
echo $this->Form->control('name', ['class' => 'form-control']);
And now the workaround in my controller:
// src/Controller/Admin/RolesController.ctp
$roleData = $this->request->getData();
$roleData['role'] = strtolower($roleData['name']);
unset($roleData['name']);
$role = $this->Roles->patchEntity($role, $roleData);
if ($this->Roles->save($role)) {
$this->Flash->success(__('The role has been saved.'));
}
It saves the entry, but doesn't fill up anything in the database row role. Am I missing something?

If you are using patchEntity then you cannot assign non assignable fields and your primary key is more than likely not an assignable key by default. You can change it in the entity which should allow the form to show it will allow patch entity to work correctly.
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Role extends Entity
{
protected $_accessible = [
'role' => true,
'permissions' => true,
'*' => false,
];
}
https://book.cakephp.org/3.0/en/orm/saving-data.html#changing-accessible-fields
https://book.cakephp.org/3.0/en/orm/entities.html#mass-assignment

In RolesTable.php there should be something like this:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('roles');
$this->setPrimaryKey('role');
}
so CakePHP would use 'role' as PrimaryKey.

Related

Foreign key constraint when using drop down in CakePHP

I am using a drop down box with a foreign key relationship. I have got the drop down filling in the correct values but the only problem is when I add a user there is a foreign key constraint. But I can make users if I just use the normal input box and type an id that exists in the other table.
For example when I enter the id with this in my add.ctp, it works:
echo $this->Form->input('location');
but when I use this it doesn't
echo $this->Form->input('location_id', array('type' => 'select', 'options' => $CompanyLocations));
This is my add function in my UsersController
public function add()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data);
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$CompanyLocations= $this->Users->CompanyLocations->find('list');
$this->set(compact('CompanyLocations'));
$this->set(compact('user'));
$this->set('_serialize', ['user']);
This is in my UsersTable
$this->belongsTo('CompanyLocations');
and my CompanyLocationsTable
public function initialize(array $config)
{
parent::initialize($config);
$this->table('company_locations');
$this->displayField('location_name');
$this->primaryKey('location_id');
$this->belongsTo('Locations', [
'foreignKey' => 'location_id',
'joinType' => 'INNER'
]);
}
and my MySQL code
CREATE TABLE IF NOT EXISTS southpac_team.company_locations (
location_id INT NOT NULL AUTO_INCREMENT,
location_name VARCHAR(45) NULL,
PRIMARY KEY (location_id))
ENGINE = InnoDB;
DROP TABLE IF EXISTS southpac_team.users ;
CREATE TABLE IF NOT EXISTS southpac_team.users (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
department INT NULL,
mobile VARCHAR(255) NULL,
email VARCHAR(255) NULL,
extension INT NULL,
lame_number INT NULL,
spa_auth_number VARCHAR(15) NULL,
creation_date DATE NULL,
picture VARCHAR(255) NULL,
employed TINYINT(1) NOT NULL,
location INT NOT NULL,
PRIMARY KEY (id),
INDEX get location_idx (location ASC),
CONSTRAINT get location
FOREIGN KEY (location)
REFERENCES southpac_team.company_locations(location_id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Naming conventions
You are not following the naming conventions, by default the foreign key name for a belongsTo association is the singular underscored variant of the association alias, postfixed with _id so in the case of CompanyLocations that would be company_location_id, not just location.
echo $this->Form->input('company_location_id');
Also the variable holding the list should use camel casing, then you don't even need to specify it via the options argument:
$companyLocations= $this->Users->CompanyLocations->find('list');
$this->set(compact('companyLocations'));
Change the association defaults
If you are working with a legacy database that you cannot modify, then you need to configure CakePHP accordingly, ie specify the custom foreign key via the options argument of Table::belongsTo().
$this->belongsTo('CompanyLocations', [
'foreignKey' => 'location'
]);
Bake gets confused
The belongsTo association in CompanyLocationsTable looks fishy too, unless you really have a LocationsTable that should be associated with CompanyLocationsTable via:
company_locations.location_id > locations.primary_key
I guess you've created the model via bake, which treated location_id as a foreign key since it matches the default foreign key naming scheme for a belongsTo association.
See also
Cookbook > CakePHP at a Glance > Conventions > Model and Database Conventions
Cookbook > Database Access & ORM > Associations - Linking Tables Together > BelongsTo Associations
Cookbook > Views > Helpers > Form > Creating Form Controls
Cookbook > Views > Helpers > Form > Creating Inputs for Associated Data

Cakephp 3 unable to save multiple translations at same time

I am trying to save multiple translations for column name in a single form submit but it always result in an exception 'name' doesn't have a default value. Below given is my implementation according to cakephp's latest documentation.
Table Structure for words
CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Table word_i18n structure that hold all the translations for table words
CREATE TABLE `word_i18n` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`locale` varchar(6) NOT NULL,
`model` varchar(255) NOT NULL,
`foreign_key` int(10) NOT NULL,
`field` varchar(255) NOT NULL,
`content` mediumtext,
PRIMARY KEY (`id`),
KEY `locale` (`locale`),
KEY `model` (`model`),
KEY `row_id` (`foreign_key`),
KEY `field` (`field`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Added the translation behavior to the WordsTable
public function initialize(array $config)
{
parent::initialize($config);
$this->table('words');
$this->displayField('name');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->addBehavior('Translate', [
'fields' => ['name'],
'translationTable' => 'word_i18n',
]);
}
/**
* Validation Rules
*/
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->requirePresence('name', 'create')
->notEmpty('name');
$validator
->notEmpty('slug')
->add('slug', 'unique', ['rule' => 'validateUnique', 'provider'=> 'table']);
return $validator;
}
Word Entity with Translation Trait
class Word extends Entity
{
use TranslateTrait;
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'*' => true,
'id' => false
];
}
Controller Method to render and handle the submission
public function add()
{
I18n::locale("en"); // Sets the default locale
$word = $this->Words->newEntity();
if ($this->request->is('post')) {
$word = $this->Words->patchEntity($word, $this->request->data, ['translations' => true]);
//debug($word);die;
if ($this->Words->save($word)) {
$this->Flash->success(__('The word has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The word could not be saved. Please, try again.'));
}
}
$this->set(compact('word'));
$this->set('_serialize', ['word']);
}
And at last the form to submit data
<?= $this->Form->create($word); ?>
<fieldset>
<legend><?= __('Add Word') ?></legend>
<?php
echo $this->Form->input('_translations.en.name',['class'=>"form-control ui-flat", "label" => __("Name [{0}]", ["English"])]);
echo $this->Form->input('_translations.ja.name',['class'=>"form-control ui-flat", "label" => __("Name [{0}]", ["Japanese"]) ]);
echo $this->Form->input('_translations.ko.name',['class'=>"form-control ui-flat", "label" => __("Name [{0}]", ["Korean"])]);
echo $this->Form->input('_translations.zh.name',['class'=>"form-control ui-flat", "label" => __("Name [{0}]", ["Chinese"])]);
echo $this->Form->button(__('Submit'),array('class'=>"btn btn-success ui-flat pull-right"));
?>
</fieldset>
<?= $this->Form->end() ?>
Everything is implement to the cakephp's documentation but always got an validation error for fields name is _required This field is required
And if remove the _translations.en from the name first form field and submits it passes the validation but leads to an sql error Field 'name' doesn't have a default value.
You’ll need to remember to add _translations into the $_accessible fields of your entity as well.
https://book.cakephp.org/3.0/en/orm/behaviors/translate.html
To save the multiple translations at same time just make sure translated columns does not exists in the table.
Here we have to remove the name from the table words.
And also remove the validation rule requirePresense for the translated column.

Kohana 3.2 validate composite primary key

I have following table
create table `groupusers`(
`id` int not null auto_increment,
`user` varchar(100) not null,
`group` varchar(100) not null,
UNIQUE KEY(`id`),
PRIMARY KEY(`user`, `group`)
)
My model looks like this,
class Model_Groupuser extends ORM{
protected $_table_name = 'groupusers';
public function rules(){
return array(
'user' => array(
array('not_empty'),
array(array($this, 'user_group_not_exists')),
),
'group' => array(
array('not_empty'),
array(array($this, 'user_group_not_exists')),
)
);
}
public function user_group_not_exists($param){
// Need to get other field's value here.
}
}
Problem is every time user_group_not_exists is called, its called with a single parameter. Either user or group. But I need both to determine if the combination exists in the db already.
How can I get current model's fields' value?
You can get other fields value using $this->object() function.
public function user_group_not_exists($user_or_group){
$obj = $this->object();
$group = $obj['group'];
$user = $obj['user'];
// Check if ($group, $user) pair exists in db here
}
You have not really named your table columns comfortable. Naming them user and group and the relations also user and group creates ambiguity between the two.
As kohana does this great thing where you can access table fields, relationships etc. as if it's an objects property. $i_am_lazy = $object-><field,relation,whatever>. Now you named your fields and relations such that it is not clear what you are trying to get.
The only way you can access these id's now is like the following (or the hard way through $this->object() as stated in the other answer, both don't feel good anyway):
$user = $this->user->id;
$group = $this->group->id;
Though, I recommend just renaming the table columns.
create table `groupusers`(
`id` int not null auto_increment,
`user_id` varchar(100) not null,
`group_id` varchar(100) not null,
UNIQUE KEY(`id`),
PRIMARY KEY(`user`, `group`)
)
That way you can simply use $this->user_id or $this->group_id.

Creating a lookup field in a CakePHP form

I have a view created using Bake that has the following:
<fieldset>
<legend><?php echo __('Edit Device'); ?></legend>
<?php
echo $this->Form->input('DeviceID');
echo $this->Form->input('DeviceTypeID');
echo $this->Form->input('UserID');
echo $this->Form->input('Type');
echo $this->Form->input('KeyPadID');
echo $this->Form->input('Version');
echo $this->Form->input('Description');
echo $this->Form->input('UpdateID');
?>
</fieldset>
Which saves to the table:
CREATE TABLE `device` (
`DeviceID` VARCHAR(255) NOT NULL ,
`DeviceTypeID` INT(11) NOT NULL ,
`UserID` INT(10) NOT NULL ,
`Type` VARCHAR(10) NULL DEFAULT NULL ,
`KeyPadID` INT(10) NULL DEFAULT NULL ,
`Version` VARCHAR(255) NULL DEFAULT NULL ,
`Description` TINYBLOB NULL ,
`UpdateID` INT(11) NULL DEFAULT NULL ,
PRIMARY KEY (`DeviceID`),
INDEX `FK_USER` (`UserID`),
INDEX `FK_devices_updates` (`UpdateID`),
INDEX `FK_device_devicetype` (`DeviceTypeID`),
CONSTRAINT `FK_device_devicetype` FOREIGN KEY (`DeviceTypeID`) REFERENCES `devicetype` (`DeviceTypeID`),
CONSTRAINT `FK_devices_updates` FOREIGN KEY (`UpdateID`) REFERENCES `update` (`ID`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `FK_USER` FOREIGN KEY (`UserID`) REFERENCES `user` (`UserID`) ON UPDATE CASCADE ON DELETE CASCADE
)
My problem is that when the form is displayed, it shows DeviceTypeID and UserID as well as UpdateID as the foreign key value instead of a drop down with the caption being the text and the value being the ID column. How would I go about setting a field from the foreign table to be the display field and the id as being the value?
Update 11-02-2013
First of all I strongly suggest to convert your primary and foreign keys accordingly
so that they meet the CakePHP naming conventions.
This means that:
DeviceID should be id.
DeviceTypeID should be device_type_id
UserID should be user_id
Also all primary keys in your tables should be named as id.
This way you will never have to worry about anything, concerning your models etc.
After that, all your tables must be in plural form. This means that device table should be devices, so you should rename it also.
I assume that you also have the following tables: devices_types and users.
At this point, I should notice that I would prefer to have a table named devicetype. I avoid underscored names, because it's very easy to make mistakes using the correct form for each object, class etc. So I don't have to worry whether I should use the CamelCase or not.
Anyway
Your Device model should be something like that:
<?php
/** Device.php **/
class Device extends AppModel {
public $name = 'Device';
public $belongsTo = array(
'DeviceType' => array(
'className' => 'DeviceType',
'foreignKey' => 'device_type_id'
/** Specify other keys that meet your needs **/
),
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
};
?>
Also your DeviceType model should be similar to
<?php
/** DeviceType.php **/
class DeviceType extends AppModel {
public $name = 'DeviceType';
};
In your edit() method, you should query your DeviceType in something like this:
...
$devicetypes = $this->Device->DeviceType->find('list', array('id', 'caption'));
$this->set(compact('devicetypes'));
...
This way in your view the respective form element sets the <select> menu correctly.
PS: You should follow the CakePHP conventions about model-naming etc... Mine was just an example.

hasMany combo box in cakePHP

I am a new cake baker. I developing a web site for cars, where each carcategory has many cars, and each car belongs only to one carcategory. In my Cars model, I wrote the following code:
class Cars extends AppModel
{
var $name = 'Cars';
var $belongsTo = array('Carcategory','User');
}
And in my Carcategory model, I wrote the following code:
class Carcategory extends AppModel {
var $name = 'Carcategory';
var $hasMany = array(
'Car' => array(
'className' => 'Car',
'foreignKey' => 'carcategory_id',
'order' => 'carcategory.name ASC',
'dependent'=> true
)
);
}
And in my car_controller, I wrote the following code:
function beforeFilter(){
$this->set('car_categories',$this->Carcategory->find('list', array('order' => 'name')));
}
And in my car view, I wrote the following code:
echo $form->input('carcategory_id', array('options' => $car_categories, 'label' => 'Cars Categories : ', 'class' => 'short'));
My database tables are as the following:
CREATE TABLE `carcategories` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`image` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE `cars` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) character set utf8 collate utf8_bin NOT NULL,
`name` varchar(255) character set utf8 collate utf8_unicode_ci NOT NULL,
`model` varchar(100) character set utf8 collate utf8_unicode_ci NOT NULL,
`motorcc` varchar(100) character set utf8 collate utf8_unicode_ci NOT NULL,
`details` text character set utf8 collate utf8_unicode_ci NOT NULL,
`carcategory_id` int(11) NOT NULL,
`userid` int(11) NOT NULL,
`hits` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
I have named the first table carcategory, because I might add another services to my site, and these services might have other categories
My code generates the following error
Notice (8): Undefined property: CarsController::$Carcategory [APP\controllers\cars_controller.php, line 14]
Fatal error: Call to a member function find() on a non-object in c:\wamp\www\work\cake\app\controllers\cars_controller.php on line 14
I cannot figure out what is the wrong in my code. Please give me a hand
Thanks
First off, you're not sticking to the CakePHP naming conventions. (Check them out here) Doing this would help you spot little mistakes, like the classname for your model is Cars when it should be Car. I would also rename your table to car_categories and hence all occurrences of Carcategory to CarCategory.
models/car.php
class Car extends AppModel {
var $name = 'Car';
var $belongsTo = array('CarCategory', 'User');
}
models/car_category.php
class CarCategory extends AppModel {
var $name = 'CarCategory';
var $hasMany = array(
'Car' => array(
'className' => 'Car',
'foreignKey' => 'car_category_id',
'order' => 'CarCategory.name ASC',
'dependent' => true
)
);
}
controllers/cars_controller.php
function beforeFilter() {
$this->set('carCategories', $this->Car->CarCategory->find('list'));
}
Note: You don't need to specify the order because you've already mentioned it in the model as the default order. Also, note the naming conventions for the view variable. This would let us do some CakePHP magic and simply type this in the view:
view
echo $form->input('car_category_id', array('class' => 'short'));
Again, not to be pedantic, but the label should be Car Category instead of Car Categories as you have it now, since a Car can belong to only one category according to your data model.
If you want the ":" separator for all your labels, you can use CSS to add them. Check here to find out how. You can also specify the between option for the form input.
Tables
Rename the table carcategories to car_categories
Rename the column carcategory_id in the cars table to car_category_id
Coming back to your actual problem:
Undefined property: CarsController::$CarCategory
This means that you cannot access CarCategory model from the CarsController. By default, the CarsController would manage the Car model. In order to manage the CarCategory model, you can do it in one of many ways:
Using associations: $this->Car->CarCategory->find( ... )
In your controller, set the $uses variable as follows: var $uses = array('Car', 'Carcategory');
You can load models on-the-fly as well (using loadModel or ClassRegistry::init if you're interested), but since you're fetching the list of Car Categories in the beforeFilter, you should stick to one of the above methods (preferably the associations).
Hope that helps. Remember to look carefully at the names of all your models, controllers, files and tables.
In your car controller the find should be
$this->set('car_categories',$this->Car->Carcategory->find('list', array('order' => 'name')));

Categories