Saving data to 2 tables in the database in CakePHP - php

I have a simple sample project created to test how to add data to 2 tables from one controller in CakePHP, I have a tables called Student, Users, Admin and UserGroups. I need to add a Student to the database table and also add the students username and password to the users table. Here are the SQL for the tables.
CREATE TABLE `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `user_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role` varchar(255) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
CREATE TABLE `admins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_group_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`post` varchar(255) DEFAULT NULL,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
I created the following function to add the data to the Students table and the Users table.
public function add() {
if ($this->request->is('post')) {
$userGrp = $this->request->data['Student']['user_group_id'];
$username = $this->request->data['Student']['username'];
$pass = $this->request->data['Student']['password'];
$this->Student->create();
if ($this->Student->save($this->request->data)) {
$this->request->data['User']['user_group_id'] = $userGrp;
$this->request->data['User']['username'] = $username;
$this->request->data['User']['password'] = $pass;
if ($this->Student->User->save($this->request->data)) {
$this->Session->setFlash(__('The student has been saved. Both in to Student and User'));
return $this->redirect(array('action' => 'index'));
}
} else {
$this->Session->setFlash(__('The student could not be saved. Please, try again.'));
}
}
$userGroups = $this->Student->UserGroup->find('list');
$this->set(compact('userGroups'));
}
The model for the Student is as follows.
<?php
App::uses('AppModel', 'Model');
class Student extends AppModel {
public $displayField = 'name';
public $belongsTo = array(
'UserGroup' => array(
'className' => 'UserGroup',
'foreignKey' => 'user_group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
When i add the Student details in to the database following error shows. Data is sent to the Student table but not to the Users table
Error: Call to a member function save() on a non-object
File: D:\wamp\www\MultiTables\app\Controller\StudentsController.php
What am i doing wrong here? please help.

IMO, students and admins should all be in a table called 'users'.
Your table that you named "users" is really a join table. That is an incorrect name for a join table if you are following conventions.
Then you would have these models:
Users (belongsTo UserGroups)
UserGroups (habtm Users via UserGroupUsers)
UserGroupUsers (belongsTo Users & belongsTo UserGroups)
If linked like that, then these inputs would save data to all three tables:
$this->Form->input('User.username')
$this->Form->input('User.password')
$this->Form->input('UserGroup.id', array('value' => 15)
You should check out CakePHP's "bake" feature. If you give it a properly constructed database, it will help you make correctly linked models.
Seems that what you're currently trying to do is:
Students (belongsTo Users)
UserGroups (habtm Students via Users & habtm Admins via Users)
Users (belongsTo Students & belongsTo Users & belongsTo Admins)
$this->Form->input('Student.username')
$this->Form->input('User.password')
$this->Form->input('UserGroup.id', array('value' => 15)

Model Student is not associated with model User.
So when you do $this->Student->User->save() with no association defined in the Student Model, You will afterwards get the error you showed because Student does not know about User.
Try defining a model Association between Student and User Model like this: A student belongs to the User and a User belongs to a UserGroup
class Student extends AppModel {
public $displayField = 'name';
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
class User extends AppModel {
public $belongsTo = array(
'UserGroup' => array(
'className' => 'UserGroup',
'foreignKey' => 'user_group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
This assumes then that you rename your Student.user_group_id into a Student.user_id in your database. Now if you want to save the User from the Student controller you can either use:
$this->Student->User->save($this->request->data)
or use the saveAssociated() method like this $this->Student->saveAssociated($this->request->data) if you named your form fields correctly

Related

Laravel Backpack: CRUD for one-many relation

I have a model called Report that has one-many relationship to another model called ReportAttachments.
In my ReportCrudController class, I have a the following field to add or manage attachments.
$this->crud->addField([ // Browse multiple
'name' => 'attachments',
'label' => 'Image Attachments',
'type' => 'browse_multiple',
'multiple' => true, // enable/disable the multiple selection functionality
'mime_types' => ['image'], // visible mime prefixes; ex. ['image'] or ['application/pdf']
'model' => 'App\Models\ReportAttachment',
'attribute' => 'id',
'entity' => 'attachments'
]);
However, I cant seem to make it work as I get this error during edit:
"Property [attachments] does not exist on this collection instance."
Also, when creating a new report, empty insert query is generated for the attachments which throws an exception:
"SQLSTATE[HY000]: General error: 1364 Field 'file' doesn't have a default value (SQL: insert into report_attachments () values ())"
This is the code for Report Model:
class Report extends Model
{
use CrudTrait;
protected $table = 'reports';
protected $fillable = ['name', 'slug', 'description', 'impression'];
protected $casts = [
'attachments' => 'array',
];
public function attachments()
{
return $this->hasMany('App\Models\ReportAttachment');
}
}
Code for Attachment Model
class ReportAttachment extends Model
{
protected $table = 'report_attachments';
protected $primaryKey = 'id';
protected $fillable = ['file', 'title', 'file_type'];
public $timestamps = false;
public function report()
{
return $this->belongsTo('App\Models\Report');
}
}
Am I missing something or doing something wrong here? Thanks in advance!
Update:
added table definition and updated namespace for model ReportAttachment
CREATE TABLE `reports` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`slug` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` longtext COLLATE utf8mb4_unicode_ci,
`impression` enum('0','1','2') COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `reports_name_unique` (`name`),
UNIQUE KEY `reports_slug_unique` (`slug`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
report_attachments table:
CREATE TABLE `report_attachments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT '',
`file` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`file_type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`report_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `report_attachments_report_id_foreign` (`report_id`),
CONSTRAINT `report_attachments_report_id_foreign` FOREIGN KEY (`report_id`) REFERENCES `reports` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Cakephp 3.0 delete not working for same table association?

I have a category table with parent category Id in the same tables. And add the association to join the same table for classified child category. Below are my table structure, table association code and delete function.But delete not working :(.
Table Structure :-
CREATE TABLE IF NOT EXISTS `category` (
`Cat_Id` int(11) NOT NULL,
`Cat_Code` varchar(255) NOT NULL,
`Cat_Desc` text NOT NULL,
`CreatedBy` varchar(255) NOT NULL,
`ParentId` int(11) NOT NULL DEFAULT '0',
`GstPercentage` double NOT NULL DEFAULT '0',
`CreatedDate` datetime NOT NULL,
`UpdateDate` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `category`
--
ALTER TABLE `category`
ADD PRIMARY KEY (`Cat_Id`),
ADD UNIQUE KEY `Cat_id` (`Cat_Id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `category`
--
ALTER TABLE `category`
MODIFY `Cat_Id` int(11) NOT NULL AUTO_INCREMENT;
Model Association :-
public function initialize(array $config)
{
parent::initialize($config);
$this->table('category');
$this->displayField('Cat_Id');
$this->primaryKey('Cat_Id');
// association for the same table join
$this->belongsToMany('Parents', [
'className' => 'Category',
'joinTable' => 'category',
'foreignKey' => 'Cat_Id',
'targetForeignKey' => 'ParentId',
]);
// used to associate the table with user table (join)
$this->belongsTo('Users', [
'className' => 'Users',
'foreignKey' => 'CreatedBy',
'propertyName' => 'user'
]);
}
Delete Function :-
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$category = $this->Category->get(base64_decode($id));
if ($this->Category->delete($category)) {
$this->Flash->success(__('The category has been deleted.'));
} else {
$this->Flash->error(__('The category could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}

User registration fields not being saved in Laravel 5

I am using Laravel 5 and have amended the default registrar a bit to use first_name and last_name instead of just name. For some reason though when the user is created the first_name and last_name fields are blank in the database. This is the code I am using:
public function validator(array $data)
{
return Validator::make($data, [
'first_name' => 'required|max:255',
'last_name' => 'required|max:255',
'discount_code' => 'max:255',
'register_email' => 'required|email|confirmed|max:255|unique:users,email',
'register_password' => 'required|confirmed|min:6|max:60'
]);
}
public function create(array $data)
{
return User::create([
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'email' => $data['register_email'],
'password' => bcrypt($data['register_password'])
]);
}
In the create method if I do a print_r($data) before User::create is called first_name and last_name are there but when the entry is stored to my users table in the database only the email and password get stored. Here is my table definition also:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL,
`first_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`last_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
`discount_code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
The first_name and last_name fields need to be mass assignable in the model through the $fillable/$guarded arrays.

Can't figure out how to associate tables with php active record

I must be missing something important but I can't sort this thing out
I would like to associate many addresses to a person so ... here we go with my settlement:
Is there anyone to help?
CREATE TABLE `testpersons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
CREATE TABLE `testaddresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
CREATE TABLE `testassociate` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`testuser_id` int(11) NOT NULL,
`testgroup_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
Then I create my objects:
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpersons';
static $has_many = array(
array('testaddress',
'through'=>'test_associate',
'foreign_key'=>'testgroup_id',
'primary_key'=>'testuser_id',
'class_name'=>'Testaddress')
);
}
class Testaddress extends ActiveRecord\Model {
static $table_name = 'addresses';
static $belongs_to = array(
array('testperson',
'through'=>'test_associate',
'foreign_key'=>'testuser_id',
'primary_key'=>'testgroup_id')
);
}
Then trying to get my result:
$person = Testperson::find(2);
echo var_dump ( $person);
Gives that:
object(Testperson)[16]
public 'errors' => null
private 'attributes' (ActiveRecord\Model) =>
array (size=2)
'id' => int 2
'name' => string 'tata' (length=4)
private '__dirty' (ActiveRecord\Model) =>
array (size=0)
empty
private '__readonly' (ActiveRecord\Model) => boolean false
private '__relationships' (ActiveRecord\Model) =>
array (size=0)
empty
private '__new_record' (ActiveRecord\Model) => boolean false
Could someone tell me what's wrong with my association?
Many thx
If all you want to do is associate many Address to one Person, than your db structure is wrong and unnecessarily complex.
What you want is this:
CREATE TABLE `testpeople` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `testaddresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`testperson_id` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
);
Now your model would be like this:
class Testaddress extends ActiveRecord\Model {
static $belongs_to = array(
array('testperson')
);
}
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpeople';
static $has_many = array(
array('testaddress')
);
}
That should be enough.
However, when you want to use through, you should have also a model for the intermediate table with the respective relationship.
For example sake, I'll use your db structure.
class Testperson extends ActiveRecord\Model {
static $table_name = 'testpeople';
static $has_many = array(
array('testassociate', 'foreign_key' => 'testuser_id'),
array('testaddress', 'through' => 'testassociate', 'foreign_key' => 'testgroup_id')
);
}
class Testassociate extends ActiveRecord\Model {
static $belongs_to = array(
array('testperson', 'foreign_key' => 'testuser_id'),
array('testaddress', 'foreign_key' => 'testgroup_id'),
);
}
class Testaddress extends ActiveRecord\Model {
static $has_one = array(
array('testassociate', 'foreign_key' => 'testgroup_id'),
array('testaddress', 'through' => 'testassociate', 'foreign_key' => 'testuser_id'),
);
}
Regarding the empty __relathionships, it's because that's a cache variable that will be populated when you request some relation, such as $person->testaddresses.

FuelPHP ORM cannot insert related table

My database:
CREATE TABLE IF NOT EXISTS `ws_accounts` (
`account_id` int(11) NOT NULL AUTO_INCREMENT,
`account_username` varchar(255) DEFAULT NULL COMMENT 'username',
`account_email` varchar(255) DEFAULT NULL COMMENT 'email',
`account_salt` varchar(255) DEFAULT NULL COMMENT 'store salt for use when hashing password',
`account_password` tinytext COMMENT 'password',
`account_display_name` varchar(255) DEFAULT NULL COMMENT 'name for display on web to prevent show username.',
`account_firstname` varchar(255) DEFAULT NULL COMMENT 'first name',
`account_middlename` varchar(255) DEFAULT NULL COMMENT 'middle name',
`account_lastname` varchar(255) DEFAULT NULL COMMENT 'last name',
`account_birthdate` date DEFAULT NULL COMMENT 'birthdate store in date format (YYYY-mm-dd)',
`account_avatar` varchar(255) DEFAULT NULL COMMENT 'avatar file. refer from root web without http or domain',
`account_signature` text COMMENT 'signature. very useful in forum',
`account_timezone` varchar(30) NOT NULL DEFAULT 'Asia/Bangkok' COMMENT 'see timezone list here http://www.php.net/manual/en/timezones.php',
`account_language` varchar(10) DEFAULT NULL COMMENT 'framework language shortcode eg: en, th',
`account_create` bigint(20) DEFAULT NULL COMMENT 'timestamp of account create date',
`account_create_gmt` bigint(20) DEFAULT NULL COMMENT 'timestamp of account create date in gmt0',
`account_last_login` bigint(20) DEFAULT NULL COMMENT 'timestamp of last login date',
`account_last_login_gmt` bigint(20) DEFAULT NULL COMMENT 'timestamp of last login date in gmt0',
`account_status` int(1) NOT NULL DEFAULT '0' COMMENT '0=disable, 1=enable',
`account_status_text` varchar(255) DEFAULT NULL COMMENT 'status text for describe why disable.',
`account_new_email` varchar(255) DEFAULT NULL COMMENT 'store new email waiting for confirmation',
`account_new_password` varchar(255) DEFAULT NULL COMMENT 'store new password in reset password progress',
`account_confirm_code` varchar(255) DEFAULT NULL COMMENT 'confirmation code. use for confirm register, change email, reset password',
`account_confirm_code_since` bigint(20) DEFAULT NULL COMMENT 'confirm code generated since',
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='contain user account' AUTO_INCREMENT=2 ;
--
-- Dumping data for table `ws_accounts`
--
INSERT INTO `ws_accounts` (`account_id`, `account_username`, `account_email`, `account_salt`, `account_password`, `account_display_name`, `account_firstname`, `account_middlename`, `account_lastname`, `account_birthdate`, `account_avatar`, `account_signature`, `account_timezone`, `account_language`, `account_create`, `account_create_gmt`, `account_last_login`, `account_last_login_gmt`, `account_status`, `account_status_text`, `account_new_email`, `account_new_password`, `account_confirm_code`, `account_confirm_code_since`) VALUES
(0, 'Guest', 'none#localhost', NULL, NULL, 'Guest', NULL, NULL, NULL, NULL, NULL, NULL, 'Asia/Bangkok', NULL, 1387121127, 1387095927, NULL, NULL, 0, 'This account is for guest actions.', NULL, NULL, NULL, NULL),
(1, 'admin', 'admin#localhost.com', NULL, '$P$FPnwJAQzX498tYCbbIfYTbdYiOCShE0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Asia/Bangkok', NULL, 1387121127, 1387095927, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL);
CREATE TABLE IF NOT EXISTS `ws_account_level` (
`level_id` int(11) NOT NULL AUTO_INCREMENT,
`level_group_id` int(11) NOT NULL,
`account_id` int(11) NOT NULL,
PRIMARY KEY (`level_id`),
KEY `level_group_id` (`level_group_id`),
KEY `account_id` (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `ws_account_level_group` (
`level_group_id` int(11) NOT NULL AUTO_INCREMENT,
`level_name` varchar(255) DEFAULT NULL,
`level_description` text,
`level_priority` int(5) NOT NULL DEFAULT '1' COMMENT 'lower is more higher priority',
PRIMARY KEY (`level_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='contain user role or level' AUTO_INCREMENT=5 ;
--
-- Dumping data for table `ws_account_level_group`
--
INSERT INTO `ws_account_level_group` (`level_group_id`, `level_name`, `level_description`, `level_priority`) VALUES
(1, 'Super administrator', 'For site owner or super administrator.', 1),
(2, 'Administrator', NULL, 2),
(3, 'Member', 'For registered user.', 999),
(4, 'Guest', 'For non register user.', 1000);
Models
class Model_Accounts extends \Orm\Model
{
protected static $_table_name = 'accounts';
protected static $_primary_key = array('account_id');
// relations
protected static $_has_many = array(
'account_level' => array(
'model_to' => 'Model_AccountLevel',
'key_from' => 'account_id',
'key_to' => 'account_id',
'cascade_delete' => true,
),
'account_fields' => array(
'model_to' => 'Model_AccountFields',
'key_from' => 'account_id',
'key_to' => 'account_id',
'cascade_delete' => true,
),
);
}
model/accounts.php
class Model_AccountLevelGroup extends \Orm\Model
{
protected static $_table_name = 'account_level_group';
protected static $_primary_key = array('level_group_id');
// relations
protected static $_has_many = array(
'account_level' => array(
'model_to' => 'Model_AccountLevel',
'key_from' => 'level_group_id',
'key_to' => 'level_group_id',
'cascade_delete' => true,
)
);
}
model/accountlevelgroup.php
class Model_AccountLevel extends \Orm\Model
{
protected static $_table_name = 'account_level';
protected static $_primary_key = array('level_id');
// relations
protected static $_belongs_to = array(
'account_level_group' => array(
'model_to' => 'Model_AccountLevelGroup',
'key_from' => 'level_group_id',
'key_to' => 'level_group_id',
),
'accounts' => array(
'model_to' => 'Model_Accounts',
'key_from' => 'account_id',
'key_to' => 'account_id',
)
);
}
model/accountlevel.php
Create user.
I use register method in accounts model to create user.
$data['account_username'] = 'unique_username';
$data['account_password'] = 'pass';
$data['account_email'] = 'no#email.tld';
$account = self::forge($data);
$account->account_level = new Model_AccountLevel();
$account->account_level->level_group_id = 3;
$account->save();
$account_id = $account->account_id;
and i got this error.
Assigned relationships must be an array or null, given relationship
value for account_level is invalid.
How to insert related table? What wrong with my code?
Your account_level relation is a has_many, this means that when accessing it you need to access it like an array. The ORM expects the value of this to be an array so assigning the model directly to it is invalid.
What you want to do is:
$account->account_level[] = new Model_AccountLevel(['level_group_id' => 3]);
(Using the array in the constructor because personally I find it's neater, but it's not required)

Categories