Codeigniter 4.1 - save() does not work as expected - codeigniter

According to the CI4 man pages, save() can be used for both update and insert. I am trying to do an update, however, it then fails stating the $post_title already exists.
Below is the code I use. $update_post already has fields like $post_body, $post_author and $post_title. Because this is an update rather than an insert, I add the post_id to the array $update_post. According to here if the array contains an existing primarykey, ci4 will perform an update. In other words, if it does not contain an existing primarykey, it will do an insert. Again, I want to do an update.
$update_post += ['post_id' => $original_post->post_id];
if ($this->model->save($update_post)){
return redirect()->to('/admin/posts/update/'.$original_post->post_id)
->with('info', 'Success - article updated!');
} else {
return redirect()->back()
->with('errors', $this->model->errors())
->with('warning', 'Invalid Data')
->withInput();;
}
Even though I am updating, ci4 returns an error stating the $post_title already exists. I have dd($update_post) and the $post_id is indeed part of the array.
Here is the model
protected $table = 'post';
protected $primaryKey = 'post_id';
//protected $useAutoIncrement = true;
protected $returnType = 'App\Entities\PostEntity';
//protected $useSoftDeletes = true;
protected $allowedFields = [
'post_id',
'post_category_id',
'post_user_id',
'post_title',
'post_slug',
'post_body',
'post_is_published',
'post_image',
];
protected $useTimestamps = true;
protected $createdField = 'post_created_at';
protected $updatedField = 'post_updated_at';
//protected $deletedField = 'deleted_at';
protected $validationRules = [
'post_category_id' => 'required',
'post_title' => 'required|is_unique[post.post_title,post_title,{post_title}]|min_length[5]|max_length[255]',
'post_body' => 'required|min_length[5]'
];
protected $validationMessages = [
'post_category_id' => [
'required' => 'The Category is required',
],
'post_title' => [
'required' => 'The title name is required',
'is_unique' => 'That title already exists',
'min_length' => 'The minimum length of a title is 5 characters',
'max_length' => 'The maximum length is 255 characters'
],
'post_body' => [
'required' => 'The body of a post is require.',
'min_length' => 'The minimum length of the body is 5 characters',
],
];
How can I properly use ci4.1's save()?
dd($update_post):
post_title => string (60) "Some title"
post_category_id => string (3) "114"
post_body => string (725) "<p>A message body</p>."
⇄post_user_id => string (3) "112"
⇄post_is_published => string (1) "0"
⇄post_image => string (19) "2245077365-huge.jpg"
⇄post_id => string (3) "314"
I also tried to type cast the post_id string with intval() but no change.
Error message is from validation as follows;
Warning! Invalid Data
That title already exists
Create and update routes:
$routes->match(['get', 'post'], '/admin/posts/create', 'Admin\Post\Post_Controller::post_create');
$routes->match(['get', 'post'], '/admin/posts/update/(:any)', 'Admin\Post\Post_Controller::post_update/$1');

Related

Laravel array key validation

I have custom request data:
{
"data": {
"checkThisKeyForExists": [
{
"value": "Array key Validation"
}
]
}
}
And this validation rules:
$rules = [
'data' => ['required','array'],
'data.*' => ['exists:table,id']
];
How I can validate array key using Laravel?
maybe it will helpful for you
$rules = ([
'name' => 'required|string', //your field
'children.*.name' => 'required|string', //your 1st nested field
'children.*.children.*.name => 'required|string' //your 2nd nested field
]);
The right way
This isn't possible in Laravel out of the box, but you can add a new validation rule to validate array keys:
php artisan make:rule KeysIn
The rule should look roughly like the following:
class KeysIn implements Rule
{
public function __construct(protected array $values)
{
}
public function message(): string
{
return ':attribute contains invalid fields';
}
public function passes($attribute, $value): bool
{
// Swap keys with their values in our field list, so we
// get ['foo' => 0, 'bar' => 1] instead of ['foo', 'bar']
$allowedKeys = array_flip($this->values);
// Compare the value's array *keys* with the flipped fields
$unknownKeys = array_diff_key($value, $allowedKeys);
// The validation only passes if there are no unknown keys
return count($unknownKeys) === 0;
}
}
You can use this rule like so:
$rules = [
'data' => ['required','array', new KeysIn(['foo', 'bar'])],
'data.*' => ['exists:table,id']
];
The quick way
If you only need to do this once, you can do it the quick-and-dirty way, too:
$rules = [
'data' => [
'required',
'array',
fn(attribute, $value, $fail) => count(array_diff_key($value, $array_flip([
'foo',
'bar'
]))) > 0 ? $fail("{$attribute} contains invalid fields") : null
],
'data.*' => ['exists:table,id']
];
I think this is what you are looking:
$rules = [
'data.checkThisKeyForExists.value' => ['exists:table,id']
];

How to set file post properly PhpUnit

I'm trying to set a test to upload a file.
In the controller I need to check if everyting is ok (form validation).
The problem is the response gives me an error $request->dataFile->getClientOriginalExtension() , (vendor/symfony/http-foundation/File/UploadedFile.php)
Looks like the dataFile, or request or.... I dont know how to set it.
/**
#test
#group formPostFile
*/
public function formPostFile()
{
$test_file_path = base_path().'/httpdocs/test/Excel.xlsx';
$this->assertTrue(file_exists($test_file_path), $test_file_path.' Test file does not exist');
$_FILE = [
'filename' => [
'name' => $test_file_path,
'type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'size' => 10336,
'tmp_name' => $test_file_path,
'error' => 0
]
];
$data = [
'id' => '2',
'dataFile' => $_FILE
];
$response = $this->post('/excel', $data);
dd($response->getContent());
}
Utilise the Symfony/Illuminate class UploadedFile
$file = new UploadedFile(
$test_file_path,
$test_file_path,
filesize($test_file_path),
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
null,
true
);
LastParameter is testMode and should be true, i believe this will work out in your code, utilise it in a similar fashion as the array you already have like so.
$data = [
'id' => '2',
'dataFile' => $file
];

How can i skip unique field in Yii2?

I have a unique field that I check when editing or adding a new training course. But, for some reason, when I enter a value in a field, it does not show me a hint that the field is already taken.
In addition, I need to do this: when I change the values and did not change this unique field, but left it as it is, then the validor should not swear that the field is already taken.
Thank.
InfCourses Model:
public function rules()
{
return [
[['name', 'short_description', 'price', 'favorite', 'active', 'course_order', 'link'], 'required'],
[['price', 'active'], 'integer'],
[['favorite'], 'string'],
[['name', 'short_description', 'link'], 'string', 'max' => 255],
[['active'], 'exist', 'skipOnError' => true, 'targetClass' => InfStatuses::className(), 'targetAttribute' => ['active' => 'id']],
[['course_order'], 'integer', 'min' => 1],
[
['course_order'], 'unique',
'targetAttribute' => ['course_order'],
'filter' => ['!=', 'id', Yii::$app->request->get('id')],
],
];
}
Validator in InfCoursesController:
public function actionValidate()
{
$model = new InfCourses();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Part of form code:
<?php $form = ActiveForm::begin([
'enableAjaxValidation' => true,
'validationUrl' => 'validate',
'options' => [
'data-pjax' => true,
]
]); ?>
Your validation is simply incorrect. You're using Yii::$app->request->get('id') in your rules, which is probably main source of your problems. Model should not access request or web user component directly - it breaks MVC pattern. Also putting values directly in rules in this way may give you unexpected results. You should check what query is generated by this validator, because it is hard to guess what is happening with such twisted rule.
But it may be easier to fix actionValidate() and distinguish between validating of new record and validating existing record:
public function actionValidate($id = null) {
if (empty($id)) {
$model = new InfCourses();
} else {
$model = $this->findModel($id);
}
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Then you can limit your unique rule to:
[['course_order'], 'unique'],
Validator will be smart enough to detect that it is validating existing record and will not report unchanged fields values as duplicates. You just need to provide record ID in this action URL.
Well... I cut code below to each action: create/update.
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
Then remove validationUrl from form component. Inside model i make this rule [['course_order'],'unique']... Working fine...

Yii2 max_size validation issue

This is my action:
public function actionCustom() {
$model = new Custom();
$model->load(\Yii::$app->request->post());
if ($model->validate()) {
// emptying the model's data
$model = new Custom();
var_dump('good');
} else {
var_dump('bad');
}
var_dump($_FILES);
return $this->render('custom', [
'model' => $model
]);
}
And this is my model:
class Custom extends Model
{
public $file;
public function rules()
{
return [
// ['file', 'file', 'extensions' => ['png', 'jpg', 'jpeg', 'gif', 'txt'], 'maxSize' => 1024 * 100]
['file', 'file', 'maxSize' => 1024 * 100],
];
}
}
When I try to upload a file which size exceeds the maxSize rule I set, the client-side validation displays an error and I can't submit the form by clicking on the button and this is all fine, but I can force submitting by typing something like this in the console like how a hacker would do:
document.forms[0].submit()
And I get this output:
C:\wamp64\www3\controllers\SiteController.php:138:string 'good' (length=4)
C:\wamp64\www3\controllers\SiteController.php:143:
array (size=1)
'Custom' =>
array (size=5)
'name' =>
array (size=1)
'file' => string 'tste.txt' (length=8)
'type' =>
array (size=1)
'file' => string 'text/plain' (length=10)
'tmp_name' =>
array (size=1)
'file' => string 'C:\wamp64\tmp\phpDE60.tmp' (length=25)
'error' =>
array (size=1)
'file' => int 0
'size' =>
array (size=1)
'file' => int 818064
string 'good' means that the file has passed the validation, but how?! The size of the file I sent was 818064 and it is bigger than 102400 (1024 * 100) file size limit which I set.
What am I doing wrong?
Have you tried to use yii\web\UploadedFile::getInstance() method mentioned in the official docs example?
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->imageFile = UploadedFile::getInstance($model, 'imageFile');
if ($model->upload()) {
// file is uploaded successfully
return;
}
}
http://www.yiiframework.com/doc-2.0/guide-input-file-upload.html#wiring-up

Validating third variable on difference between 2 other variables in cakephp

I can't get my head around this one. I want to validate whether a third variable on a cakephp form is less than or equal to the difference between two other variables on the form. Has anyone tackled this or similar?
Paul
here is some code to show what I have done:
public function lessThanEqualTo($check, $otherfield) {
$value = array_values($check);
$compareTo = $this->data[$this->name][$otherfield];
if (!Validation::comparison($value[0], 'lessorequal', $compareTo)) {
return false;
}
else {
return true;
}
}
public function numDifference($startnumber, $usednumber) {
$sn = int($this->data[$this->name][$startnumber]);
$un = int($this->data[$this->name][$usednumber]);
return ($sn - $un);
}
Model contains validation: but the second rule is plain wrong, I have tried a number of things, but I am just coming up with rubbish:
public $validate = array(
'ag1_compl_dist_num'=>array(
'rule' => array('lessThanEqualTo','ag1_compl_start_number'),
'message' => 'Value must be less than starting number',
'allowEmpty' => true,
'required' => false
),
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualTo','numDifference'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);
Clearly, the call to numDifference should have something that identifies which two numbers to work out the difference between..
When you do:
'rule' => array('lessThanEqualTo','numDifference')
And then in your lessThanEqualTo function $compareTo = $this->data[$this->name][$otherfield];, you will look for numDifference field in your data object, which does not exist.
One way would be to create a lessThanEqualToDiff method like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = intval($this->data[$this->name][$startfield]);
$un = intval($this->data[$this->name][$usedfield]);
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
Then your rule:
public $validate = array(
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualToDiff', 'ag1_compl_start_number', 'ag1_compl_used_number'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);
So, the validation method should be like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = (int)$this->data[$this->name][$startfield];
$un = (int)$this->data[$this->name][$usedfield]
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
to use the php int feature correctly. Thanks to Holt and marian0 for their help.

Resources