Insert into Database not working due to Relations in Database - php

I've been trying to make an insert into the database whenever a user register, but I always got an SQL[23000] error and I realized that inside my database, there was a relationship to a different table and that is why I was getting an error. I'm used to creating a model and crud through Gii but this is the first time where I encountered an error because of relationships between tables. I think the problem is that I need to be able to insert into two models and I'm not completely sure how I should do that.
First things first, I'll show my schema:
CREATE TABLE IF NOT EXISTS `system_users` (
`party_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(255) NOT NULL,
`date_last_login` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` varchar(50) NOT NULL DEFAULT 'Pending for Approval',
`date_created` datetime NOT NULL,
`date_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`user_role` varchar(255) NOT NULL,
`isLogin` int(1) NOT NULL,
PRIMARY KEY (`party_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=219 ;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `system_users`
--
ALTER TABLE `system_users`
ADD CONSTRAINT `system_users_ibfk_1` FOREIGN KEY (`party_id`) REFERENCES `parties` (`id`);
---------------------------------------
CREATE TABLE IF NOT EXISTS `parties` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`party_type_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `party_type_id` (`party_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=200 ;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `parties`
--
ALTER TABLE `parties`
ADD CONSTRAINT `parties_ibfk_1` FOREIGN KEY (`party_type_id`) REFERENCES `party_types` (`id`);
After this, I generated a model using Gii and I called it SystemUsers.php and I also generated the crud into the systemUsers under view.
Now problem is, every time I select "Create," it throws me an error that it cannot somehow find the parties id.
Just in case, here is the code of my model SystemUsers.php:
<?php
class SystemUsers extends CActiveRecord
{
/**
* Returns the static model of the specified AR class.
* #param string $className active record class name.
* #return SystemUsers the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'system_users';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('username, password, date_last_login, date_created, user_role, isLogin', 'required'),
array('isLogin', 'numerical', 'integerOnly'=>true),
array('username', 'length', 'max'=>200),
array('password, user_role', 'length', 'max'=>255),
array('status', 'length', 'max'=>50),
array('date_modified', 'safe'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('party_id, username, password, date_last_login, status, date_created, date_modified, user_role, isLogin', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'party_id' => array(self::BELONGS_TO, 'system_users', 'party_id'),
'party_id' => array(self::HAS_ONE, 'parties', 'id'),
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'party_id' => 'Party',
'username' => 'Username',
'password' => 'Password',
'date_last_login' => 'Date Last Login',
'status' => 'Status',
'date_created' => 'Date Created',
'date_modified' => 'Date Modified',
'user_role' => 'User Role',
'isLogin' => 'Is Login',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* #return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('party_id',$this->party_id,true);
$criteria->compare('username',$this->username,true);
$criteria->compare('password',$this->password,true);
$criteria->compare('date_last_login',$this->date_last_login,true);
$criteria->compare('status',$this->status,true);
$criteria->compare('date_created',$this->date_created,true);
$criteria->compare('date_modified',$this->date_modified,true);
$criteria->compare('user_role',$this->user_role,true);
$criteria->compare('isLogin',$this->isLogin);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
}

You have to implement a multi-step form and a single fatty controller which does all pluming working for you e.g adding/updating all three entities . please see this thread

as u have foreign key Constraints then you have have entry in parent table
system_users -> depends on parties
and
parties -> depends on party_types
so to insert the record in system_user you must have a record in Parites and similarly to insert the record in parties you must have a record in party_types
so first insert the record in party_type and for that party_type create record in Parties and then for that party_id create the record in system_user

Related

Seeding database with random states in Laravel

I have a Laravel 8 application and I want to be able to seed one of my tables with different states when I execute php artisan db:seed. Here's an example table:
Create Table: CREATE TABLE `notifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`message` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`is_read` tinyint(1) NOT NULL DEFAULT '0',
`recipient_role` enum('Manager','Employee') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Manager',
`recipient_id` bigint(20) unsigned NOT NULL,
`sender_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `notifications_recipient_id_foreign` (`recipient_id`),
KEY `notifications_sender_id_foreign` (`sender_id`),
CONSTRAINT `notifications_recipient_id_foreign` FOREIGN KEY (`recipient_id`) REFERENCES `users` (`id`),
CONSTRAINT `notifications_sender_id_foreign` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
In this particular case, I want to create several Notification instances using Notification::factory where is_read is random set to 1 OR 0. I also would like to make sure that two random User instances are selected (they will be seeded first) for the recipient_id and sender_id. This means I cannot hard code them into my Factory.
In the actual factory I only have message being filled in:
<?php
namespace Database\Factories;
use App\Models\Notification;
use Illuminate\Database\Eloquent\Factories\Factory;
class NotificationFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Notification::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
//
'message' => $this->faker->sentence,
];
}
}
My question is, in the NotificationSeeder.php definition, how do I ensure that I have a random is_read state and two different users assigned to recipient_id and sender_id?
I don't have experience with Laravel 8, but I think it should work:
NotificationFactory:
public function definition()
{
// Get 2 random users IDs
$users = User::inRandomOrder()->take(2)->pluck('id');
return [
'message' => $this->faker->sentence,
'is_read' => rand(0, 1),
'recipient_id' => $users->first(),
'sender_id' => $users->last(),
];
}
If you create the users first, then just get 2 random users. I did 1 call to get 2 users instead of 1 call per user. That returns a collection of users IDs, get the first one for "recipient_id" and the last one to "sender_id".
Use faker's numberBetween function to get a random number between 0-1.
https://github.com/fzaninotto/Faker#fakerproviderbase
<?php
namespace Database\Factories;
use App\Models\Notification;
use Illuminate\Database\Eloquent\Factories\Factory;
class NotificationFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Notification::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
//
'message' => $this->faker->sentence,
'is_read' => $this->faker->numberBetween(0, 1),
];
}
}
Then when you're creating the notifications, first create your user's, then provide them to the make/create method.
// create two users
$first = User::factory()->count(1)->create();
$second = User::factory()->count(1)->create();
// override the default notification attributes with the user's id
$notification = Notification::factory()->count(1)->create([
'recipient_id' => $first->id,
'sender_id' => $second->id,
]);

Cakephp 3 create entry, set custom primary field

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.

Yii2 Linking two models not working when saving user-submitted data from form

I have two models: one for "Experts" (based on a database table of their contact details), and one for "Expertise" (such as 'PHP', 'JavaScript', 'Java', ..., also in database table). Since an expert can have more than one expertise, and an expertise can be held by several experts, this is a many-to-many relationship that is defined in the model classes, see below. The relationship should be stored in a junction table.
// in models/RcccExperts.php
class RcccExperts extends \yii\db\ActiveRecord
{
...
public function getRcccExpertise()
{
return $this->hasMany(RcccExpertise::className(), ['id' => 'expertise_id'])
->viaTable('rccc_experts_expertise', ['expert_id' => 'id']);
}
}
// in models/RcccExpertise.php
class RcccExpertise extends \yii\db\ActiveRecord
{
...
public function getRcccExperts()
{
return $this->hasMany(RcccExperts::className(), ['id' => 'expert_id'])
->viaTable('rccc_experts_expertise', ['expertise_id' => 'id']);
}
}
The user can add a new entry to the experts database via a form that contains data from both models. The user can assign expertise to the expert via a multiple select form field (implemented with select2 tags), where s/he can select from existing expertise (already in the database) or add new expertise tags.
When I try to link the two models to populate the junction table (that contains the expert IDs and expertise IDs), I get the following error message:
PHP Fatal Error – yii\base\ErrorException
Call to a member function getIsNewRecord() on array
in /Users/Sites/Yii/vendor/yiisoft/yii2/db/BaseActiveRecord.php at line 1248
I'm aware that when linking two models using ->viaTable, both models mustn't be newly created. In my case, the Expert model has just been saved and has a primary key id; I'm retrieving the Expertise ids that were selected for the Expert from the database.
Whichever way I try to link the two models, the problem seems to be that the Expertise is not recognized as an existing model, but as an array().
What am I doing wrong?!
More details on the error below:
* This parameter is only meaningful for a relationship involving a junction table
* (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].)
* #throws InvalidCallException if the method is unable to link two models.
*/
public function link($name, $model, $extraColumns = [])
{
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.');
}
if (is_array($relation->via)) {
/* #var $viaRelation ActiveQuery */
list($viaName, $viaRelation) = $relation->via;
$viaClass = $viaRelation->modelClass;
// unset $viaName so that it can be reloaded to reflect the change
unset($this->_related[$viaName]);
} else {
...
After the form to create a new expert has been submitted, this is the Controller code to execute:
// in controllers/ExpertsController.php
/**
* ExpertsController implements the CRUD actions for RcccExperts model.
*/
class ExpertsController extends Controller
{
...
public function actionCreate()
{
$model = new RcccExperts();
$expertise = new RcccExpertise();
if ($model->load(Yii::$app->request->post()) && $expertise->load(Yii::$app->request->post())) {
if ($model->validate() && $expertise->validate()) {
if ($model->save()) {
// Once the Expert's model data has been saved
// Go ahead and process the Expertise ids form the multiple select form field:
// First save the expertise (coming from a multiple select form field where the user can
// select existing expertise tags or add new ones) to the database
$array = $expertise->expertise;
$expertise_ids = array();
foreach ($array as $key => $value) {
$exp = new RcccExpertise();
// Check that the expertise does not exist yet
$check1 = $expertise->find()->where(['id' => $value])->one();
$check2 = $expertise->find()->where(['expertise' => $value])->one();
if ($check1 == null && $check2 == null) {
$exp->expertise = $value;
// Save new expertise
$exp->save();
$result = $expertise->find()->select('id')->where(['expertise' => $value])->one();
$expertise_ids[] = $result->id;
}
else $expertise_ids[] = $value;
}
$expertise->id = $expertise_ids;
// Put the new expertise IDs in a model instance
$expertises = RcccExpertise::find()->where(['id' => $expertise->id])->all();
// Link expert model with expertise model
// to populate the junction table
$model->link('rcccExpertise', $expertises);
}
return $this->redirect(['view', 'id' => $model->id]);
}
}
else {
return $this->render('create', [
'model' => $model,
'expertise' => $expertise,
'languages' => $languages,
'attachments' => $attachments
]);
}
}
The MySQL tables holding this information look like this:
`CREATE TABLE `rccc_experts` (
`id` int(10) UNSIGNED NOT NULL,
`name` varchar(200) NOT NULL,
`email` varchar(100) NOT NULL,
`phone` varchar(100) NOT NULL,
`skype` varchar(100) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_experts`ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_experts` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
CREATE TABLE `rccc_expertise` (
`id` int(10) UNSIGNED NOT NULL,
`expertise` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_expertise` ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_expertise` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
CREATE TABLE `rccc_experts_expertise` (
`id` int(10) UNSIGNED NOT NULL,
`expert_id` int(10) UNSIGNED NOT NULL,
`expertise_id` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `rccc_experts_expertise` ADD PRIMARY KEY (`id`);
ALTER TABLE `rccc_experts_expertise` MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;COMMIT;
Relation hasMany returns array of objects. To make it work, you have to loop over this array, like:
foreach ($model->rcccExperts as $expert) {
// do your stuff here on $expert variable
}

SQL not inserting into table with relation in Yii

I'm trying to create a user and all the values are not inserting into the database. The systems_user table has a relation to a parties table as the party_id is the primary key of the sytems_user. Nothing is being inserted. Not even an error, it just goes back to the "create" page. Here is my schema:
--
-- Table structure for table `system_users`
--
CREATE TABLE IF NOT EXISTS `system_users` (
`party_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(255) NOT NULL,
`date_last_login` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` varchar(50) NOT NULL DEFAULT 'Pending for Approval',
`date_created` datetime NOT NULL,
`date_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`user_role` varchar(255) NOT NULL,
`isLogin` int(1) NOT NULL,
PRIMARY KEY (`party_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=221 ;
--
-- Constraints for table `system_users`
--
ALTER TABLE `system_users`
ADD CONSTRAINT `system_users_ibfk_1` FOREIGN KEY (`party_id`) REFERENCES `parties` (`id`);
-------------------------------------
-- Table structure for table `parties`
--
CREATE TABLE IF NOT EXISTS `parties` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`party_type_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `party_type_id` (`party_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=200 ;
--
-- Constraints for table `parties`
--
ALTER TABLE `parties`
ADD CONSTRAINT `parties_ibfk_1` FOREIGN KEY (`party_type_id`) REFERENCES `party_types` (`id`);
Where have I gone wrong? Why is it not inserting?
EDIT
Rules for SystemUser Model:
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('username, password, date_last_login, date_created, user_role, isLogin', 'required'),
array('isLogin', 'numerical', 'integerOnly'=>true),
array('username', 'length', 'max'=>200),
array('password, user_role', 'length', 'max'=>255),
array('status', 'length', 'max'=>50),
array('date_modified', 'safe'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('party_id, username, password, date_last_login, status, date_created, date_modified, user_role, isLogin', 'safe', 'on'=>'search'),
);
}
Rules for Parties:
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('party_type_id', 'required'),
array('party_type_id', 'length', 'max'=>10),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, party_type_id', 'safe', 'on'=>'search'),
);
}
EDIT Controller actionCreate()
A little background: as you can see from below, the conditions inside the if statement is only asking if the SystemUsers is set because the create come from the SystemUser form. My goal is to get the party_id of the system_user and insert it into the Parties table which is a different model from the SystemUsers that i'm using. So far when I run this, nothing happens.
public function actionCreate()
{
$parties = new Parties;
$model= new SystemUsers;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);
if (isset($_POST['SystemUsers']))
{
$parties->attributes = (HOW can I put the party_id from $model here?);
$model->attributes = $_POST['SystemUsers'];
$valid = true;
$valid &= $parties->validate();
$valid &= $model->validate();
if($valid)
{
$parties->id = $model->getPrimaryKey();
$parties->save(); /* First save parties. */
$model->save();
}
}
$this->render('create',array(
'model'=>$model,
'parties'=>$parties,
));
Yii doesn't insert not-safe values in database, I had this problem before, if you make all attributes safe, you will be fine. your attributes now are safe only in search scenario ( 'on'=>'search' ) . for making all attributes safe in all scenarios, remove 'on'=>'search' from both model rules.
You placed $parties->id = $customers->getPrimaryKey(); after $parties->save();.
if you want to save $parties->id too, place it before save()
Remove redirect line, I think your save() method cannot show errors because of that.
update :
What about this ?
if (isset($_POST['SystemUsers'])) {
$model->attributes = $_POST['SystemUsers'];
if($model->save()){ // save() does validation in itself
$parties->id = $model->getPrimaryKey();
$parties->save();
}
}

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.

Categories