In cakephp3 Custom Validation Rules:
How to Use a global function validation method.
$validator->add('title', 'custom', [
'rule' => 'validate_title'
]);
Please any one done before? Pls Provide me the some example program.
http://book.cakephp.org/3.0/en/core-libraries/validation.html#custom-validation-rules
I tried the above but it doesn't work..?
here is an Example for validation using global function concept:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
public function validationDefault(Validator $validator) {
$validator->add('title',[
'notEmptyCheck'=>[
'rule'=>'notEmptyCheck',
'provider'=>'table',
'message'=>'Please enter the title'
]
]);
return $validator;
}
public function notEmptyCheck($value,$context){
if(empty($context['data']['title'])) {
return false;
} else {
return true;
}
}
<?php
namespace App\Model\Table;
use App\Model\Entity\Member;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class MembersTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('members');
}
public function validationDefault(Validator $validator) {
$validator
->add("cedula", [
"custom" => [
"rule" => [$this, "customFunction"], //add the new rule 'customFunction' to cedula field
"message" => "Enter the value greater than 1000"
]
]
)
->notEmpty('cedula');
return $validator;
}
public function customFunction($value, $context) {
return $value > 1000;
}
}
Use $context variable to comare current value with other fields like $value >= $context['data']['another_field_name'];
?>
Use $context variable to comare current value with other fields like $value >= $context['data']['another_field_name'];
This actually work for me (Cakephp 3.x). It's a good way if your condition is simple:
<?php
namespace App\Form;
use Cake\Form\Form;
use Cake\Validation\Validator;
class addPostForm extends Form {
protected function _buildValidator(Validator $validator) {
return $validator->allowEmpty('my_input', function ($context) {
return (#context['data']['an_other_input'] != "");
});
}
public function setErrors($errors) {
$this->_errors = $errors;
}
}
Here the form input my_input is allow to be empty only if a second input an_other_input is completed.
You can get form data with the variable $context['data'].
This is what worked for me for CakePHP 3.0. The important parameter here is the 'provider', which is not very clear in the document examples.
$validator->add('title', 'custom', [
'rule' => 'validate_title',
'provider' => 'table',
'message' => 'some error message'
]);
Then define your function. The variable passed to the function is
$check='title'
:
public function validate_title($check)
{
...
}
here is an Example for validation.
In your Table.
public function validationDefault(Validator $validator)
{
$validator = new Validator();
$validator
->notEmpty('username', 'A username is required')
->add('username', [
'emailValid' => [
'rule' => ['email', true],
'message' => 'You must provide a valid email'
],
'emailUnique' => [
'message' => 'The email you provided is already taken. Please provide another one.',
'rule' => 'validateUnique',
'provider' => 'table'
]
]);
return $validator;
}
Related
I have this Rules in my Laravel-5.8
Rules: create
public function rules()
{
return [
'location_name' => [
'required',
'string',
'min:3',
'max:80',
Rule::unique('hr_work_locations', 'location_name', 'company_id')
],
];
}
Rules: edit
public function rules()
{
return [
'location_name' => [
'required',
'string',
'min:3',
'max:80',
Rule::unique('hr_work_locations', 'location_name', 'company_id')->ignore($this->work_location)
],
];
}
from the rules,location_name is unique for each company (company_id). Also in the edit rules,
ignore($this->work_location)
is for the route
Controller : create
public function store(StoreWorkLocationRequest $request)
{
try {
$worklocation = HrWorkLocation::create([
'location_name' => $request->location_name,
'is_active' => 1,
]);
Session::flash('success', 'Work Location is created successfully');
return redirect()->route('hr.work_locations.index');
}
catch (Exception $exception)
{
Session::flash('error', 'Action failed!');
return redirect()->route('hr.work_locations.index');
}
}
I observe that it allows location_name as England or england.
How do I make Rule::unique as case sensitive?
Thank you.
Add a custom validation to AppServiceProvider.php under the boot() method:
Validator::extend('iunique', function ($attribute, $value, $parameters, $validator) {
$query = DB::table($parameters[0]);
$column = $query->getGrammar()->wrap($parameters[1]);
return ! $query->whereRaw("lower({$column}) = lower(?)", [$value])->count();
});
This can be extended further to accept the other parameters similar to the unique rule
Now my $rules looks like this:
protected $rules = [
'username' => 'required|alpha_dash|min:5|max:18|iunique:users,username',
];
see an issue reported here
You can make the input lower-cased so you don't have to worry of what the user input in the form.
use Illuminate\Support\Str;
After that, you can call it without the namespace prefix:
Str::lower($request->location_name);
I am having problems displaying custom error messages.
I received a training project that had the following code:
class StoreProject extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|unique:projects,name|max:255',
'website' => 'url',
];
}
public function messages()
{
return [
'name' => 'Це імʼя вже використовується',
'website' => 'Будь-ласка введіть адресу вашого сайту вірно http://...'
];
}
}
I added the function message( ) myself.
This is the controller code:
public function store(StoreProject $request)
{
$project = new Project($request->except('project_image'));
$project->owner_id = Auth::user()->id;
$project->status_id = StatusProject::UNCONFIRMED;
//send email to moderator and accountant for the moderation
if( $project->save() ) {
$this->dispatch(new ConfirmNewProject($project));
}
// load image from cropie serves
if ($request->has('project_image')) {
$file = self::croppie($request->input("project_image"));
$project->uploadImage($file, 1);
}
return redirect()->route('projects.show', [$project->id]);
}
I tried various methods: withErrors([]) and this method:
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
'name' => 'Це імʼя вже використовується',
'website' => 'Будь-ласка введіть адресу вашого сайту вірно http://...'
],
]
but when checking, I get the key value, not the text of the error message
Errors:
validation.unique
validation.url
How to transfer the rule and message to the controller?
Try to modify the messages() function like that one:
public function messages()
{
return [
'name.required' => 'Name required message',
'name.unique' => 'Name unique message',
'name.max' => 'Name max message',
'website.url' => 'Будь-ласка введіть адресу вашого сайту вірно http://...'
];
}
I have custom validation rule appointment_status. I am performing various test cases on it and decide what error message is best and throwback. it will be different for every case. I want the $validator->errors()->add('status', __('Invalid status for an appointment in past') to set the error message and it's adding. but it's not returning back to the controller. I can't access this message anywhere. it shows only the status.appointment_status one which is set in messages() function.
Custom Request class:
namespace Modules\ShopManager\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class AppointmentsRequest extends FormRequest
{
public function __construct()
{
\Validator::extend('appointment_status', 'Modules\ShopManager\Validators\CustomValidator#appointmentStatus');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = [
'services' => 'required',
'sdate' => 'required|date_format:m-d-Y|time_range:sTime,edate,eTime,timezone',
'edate' => 'required|date_format:m-d-Y|workinghours_range:sdate,sTime,edate,eTime,timezone',
'sTime' => 'required|date_format:h:i a',
'eTime' => 'required|date_format:h:i a',
'cname' => 'required',
'cphone' => 'required_without:cemail',
'cemail' => 'nullable|required_without:cphone|email',
'timezone' => 'required',
'status' => 'required|appointment_status:sdate,sTime,edate,eTime,timezone',
];
return $rules;
}
public function messages()
{
return [
'status.appointment_status' => 'Invalid status'
];
}
public function attributes()
{
return [
'services' => 'Services',
'date' => 'Date',
'sTime' => 'Start Time',
'eTime' => 'End Time',
'cname' => 'Customer name',
'cphone' => 'Customer phone',
'cemail' => 'Customer email',
'internal_note' => 'Internal note',
'status' => 'Status',
];
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
}
The custom validator function:
public function appointmentStatus($attribute, $value, $parameters, $validator)
{
$dateTimeOperations = new DateTimeOperations;
$sdate = array_get($validator->getData(), $parameters[0]);
$startTime = array_get($validator->getData(), $parameters[1]);
$edate = array_get($validator->getData(), $parameters[2]);
$endTime = array_get($validator->getData(), $parameters[3]);
$timezone = array_get($validator->getData(), $parameters[4]);
$now = $dateTimeOperations->getNow($timezone);
$start = $dateTimeOperations->getTimestamp($sdate, $startTime, $timezone);
$end = $dateTimeOperations->getTimestamp($edate, $endTime, $timezone);
switch ($value) {
case constants('appointments.status.pendig'):
$start->lessThan($now)
? $validator->errors()->add('status', __('Invalid status for an appointment in past'))
: '';
}
return $validator->errors()->any();
}
Adding an error just to the field without specifying the rule I don't think will work, that's why the message from the validation request takes precedence.
So change it to this:
$validator->errors()->add('status.appointment_status', __('Invalid status for an appointment in past'))
And also in your case do you maybe have a typo: pendig to be pending?
You have to create custom validator rules and add below code inside your rule wherever required, See example below:
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
I want to test my AR model without connect to database in Yii 2 so I use mockBuilder() but I dont know how can I pass the mock object to the model exist validator, for example:
class Comment extends ActiveRecord
{
public function rules()
{
return [
[['id', 'user_id', 'post_id'], 'comment'],
['comment', 'string',
'max' => 200
],
['user_id', 'exist',
'targetClass' => User::className(),
'targetAttribute' => 'id'
],
['post_id', 'exist',
'targetClass' => Post::className(),
'targetAttribute' => 'id'
]
];
}
}
class CommentTest extends TestCase
{
public function testValidateCorrectData()
{
$user = $this->getMockBuilder(User::className())
->setMethods(['find'])
->getMock();
$user->method('find')->willReturn(new User([
'id' => 1
]));
$post = $this->getMockBuilder(Post::className())
->setMethods(['find'])
->getMock();
$post->method('find')->willReturn(new Post([
'id' => 1
]));
// How can I pass to $user and $post to exist validator in Comment model?
$comment = new Comment([
'user_id' => 1,
'post_id' => 1,
'comment' => 'test...'
]);
expect_that($comment->validate());
}
}
ok, It's not a best code just I'd like to introduce what I want to do.
Yii2 ExistValidator uses ActiveQuery::exists() for check existence and you should replace generated validator to mockobject where the method createQuery returns mockobject of ActiveQuery where ::exists() return something you want (true/false) e.g.
$activeQueryMock = $this->getMockBuilder(ActiveQuery::className())
->disableOriginalConstructor()
->setMethods(['exists'])
->getMock();
$activeQueryMock->expects($this->any())
->method('exists')
->willReturn($value); //Your value here true/false
$model = $this->getMockBuilder(Comment::className())
->setMethods(['getActiveValidators'])
->getMock();
$model->expects($this->any())
->method('getActiveValidators')
->willReturnCallback(function () use ($activeQueryMock) {
$validators = (new Comment())->activeValidators;
foreach ($validators as $key => $validator) {
if (($validator instanceof ExistValidator) && ($validator->attributes = ['user_id'])) {
$mock = $this->getMockBuilder(ExistValidator::className())
->setConstructorArgs(['config' => get_object_vars($validator)])
->setMethods(['createQuery'])
->getMock();
$mock->expects($this->any())
->method('createQuery')
->willReturn($activeQueryMock);
$validators[$key] = $mock;
break;
}
}
return $validators;
});
$model->validate();
I want to set the values of my table column set as unique value, how i can use to set error if in insert form, I insert the same value as data in my database?
Is it true?
public function rules()
{
return [
[['nama_barang', 'harga', 'stok', 'id_satuan'], 'required'],
[['harga', 'stok', 'id_satuan'], 'integer'],
['nama_barang', 'unique', 'targetAttribute' => ['nama_barang' => 'nama_barang']],
[['foto'], 'safe']
];
}
Remember: model, view, controller.
Model
add unique validator in your model rules like
...
[['nama_barang'], 'unique'],
...
View
Enable ajax validation in your form view
...
<?php $form = ActiveForm::begin(['enableAjaxValidation' => true]); ?>
...
Controller
Add ajax validation in your controller
Create Action
...
public function actionCreate()
{
$model = new Product();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
if ($model->load(Yii::$app->request->post())) {
...
and Update Action
...
public function actionUpdate($id)
{
$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);
}
if ($model->load(Yii::$app->request->post())) {
...
PS: if not present, add required classes in your controller.
use yii\web\Response;
use yii\widgets\ActiveForm;
Try this way
public function rules()
{
return [
[['nama_barang', 'harga', 'stok', 'id_satuan'], 'required'],
[['harga', 'stok', 'id_satuan'], 'integer'],
['nama_barang', 'unique', 'targetAttribute' => ['nama_barang'], 'message' => 'Username must be unique.'],
[['foto'], 'safe']
];
}
Just set unique in the rules [['name'], 'unique'],
Below is the complete function.
public function rules()
{
return [
[['name', 'description', 'comp_id'], 'required'],
[['description'], 'string'],
[['comp_id'], 'integer'],
[['name'], 'string', 'max' => 100,],
[['name'], 'unique'],
[['comp_id'], 'exist', 'skipOnError' => true, 'targetClass' => Company::className(), 'targetAttribute' => ['comp_id' => 'comp_id']],
];
}
I had a similar problem whereby when I insert a record with an existing unique field the framework remained silent returning my view back without any error.
So, the trick around this was to do a success-redirect only when $model->save() has a boolean value of true, else render back the _form.php through your view.php