Cakephp 3 unable to save multiple translations at same time - php

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.

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,
]);

default() is not working in laravel migration

I am creating a to do list using laravel, and I have a table with the columns below:
'title', 'description', 'completed', 'id'
The 'completed' column is set automatically whenever I add a new task to my table, its default value is 0 and it's being coded like this:
$table->boolean('completed')->default(0);
This column works perfectly fine, but when I try to give 'description' a default value (by leaving an empty input for description), it gives me a 'Integrity constraint violation: 1048 Column 'description' cannot be null' error.
The code I have written for 'description' column is the code below:
$table->string('description')->default('-');
I tried the text() data type, but it gives me the same error.
I also tried using nullable() for this column, but the default value, '-' , doesn't get added to the column and it returns a emtpy value.
The form I'm using to add the values looks like this:
<form action="/create" class="panel" method="post">
#csrf
<input name="title" type="text" class="title">
<textarea name="description" class="title"></textarea>
<input type="submit" class="submit">
</form>
My controller store method:
public function createTask(validation $request){
$userId = auth()->id();
$request['user_id'] = $userId;
Task::create($request->all());
return redirect()->route('showList');
}
and the table create statement:
CREATE TABLE `tasks` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`completed` tinyint(1) NOT NULL DEFAULT 0,
`user_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ' ',
PRIMARY KEY (`id`),
KEY `tasks_user_id_foreign` (`user_id`),
CONSTRAINT `tasks_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
After seeing the create table statement and your store function. It looks like it is more likely that your store function is the problem here. Try this:
use Illuminate\Support\Facades\Auth;
public function createTask(Request $request){
$user = Auth::user();//get the user model
$request->validate([//validate that the data is according to limitations
'title' => 'required|max:255',
'description' => 'max:1000',
]);
$task = new Task; //create a new instance of Task
//fill in the data
$task->title = $request->input('title');
if(!empty($request->input('description')){
$task->description = $request->input('description');
}
$task->user_id = $user->id;
$task->save();//save it
return redirect()->route('showList');
}
In your code you use mass assignment described in detail here
For mass assignment you should define your database fields to be mass assignable in your Model with protected $fillable = ['title','description','user_id'];
Your problem in specific tho is that laravel sets empty inputs to null according to answer in this question: Mass assignment won't handle Null input even when default is set on migration.Any solution to this?
You should always validate the user input and laravel provides robust tools for doing just that: https://laravel.com/docs/7.x/validation
You also might wanna consider using laravels built-in default values or at least check if they are intervening with the data :How to set the default value of an attribute on a Laravel model

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.

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

Insert into Database not working due to Relations in Database

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

Categories