I am creating a 'form editor' in CakePHP.
The interface allows the user to choose validations to apply to the fields, e.g. Numeric, Email etc.
As such I need to dynamically create validation for the model based on the user input. For this I can use the Validation object: https://book.cakephp.org/3.0/en/core-libraries/validation.html
I want to take advantage of the features of FormHelper, for example, automatically outputting error messages for fields.
I can see how to use a hard-coded validator from the model to do this by setting the validator in the context option for Form->create() - but how do I use a customer $Validator object which has been dynamically created?
Clarification:
I have some code in my controller:
//get the form configuration
$form = $this->Forms->get($id, ['contain'=>['FormFields'=>['Validations']]]);
//set up validation based on the configuration
$validator = new Validator();
foreach($form->form_fields as $field){
...
if($field->required) $validator->notBlank($field->field_name);
}
$table = TableRegistry::get($form->type);
$table->setValidator($validator);
Unfortunately setValidator() is not a method of TableRegistry.
If I set the validation up in the model, I would need the $id parameter to look up the correct form configuration.
I added the following code to my model:
protected $validator = null;
public function validationDefault(Validator $validator){
if($this->validator != null) return $this->validator;
return $validator;
}
public function setValidator(Validator $validator){
$this->validator = $validator;
}
So the default validator can effectively be set via the setValidator method.
Then in my controller:
//get the form configuration
$form = $this->Forms->get($id, ['contain'=>['FormFields'=>['Validations']]]);
//set up validation based on the configuration
$validator = new Validator();
foreach($form->form_fields as $field){
...
if($field->required) $validator->notBlank($field->field_name);
}
$table = TableRegistry::get($form->type);
$table->setValidator($validator);
I hope this is useful for others.
Related
In My project there is a PackageRequest model and this model have a tracking_code.
I want to set this attribute automatically with a mutator.
this is my mutator:
public function setTrackingCodeAttribute()
{
$code = mt_rand(1000000, 9999999);
$this->attributes['tracking_code'] = "$code";
}
but when I save new request into database there is no tracking_code
this is my part of code that create new PackageRequest:
public function store(Request $request)
{
$input = $request->only(['requested_time', 'address_id']);
$valid = validator($input, [
'requested_time' => 'required|date',
'address_id' => 'required|numeric|exists:addresses,id'
]);
if ($valid->fails())
return Response::fail($valid->errors());
$package_request = new PackageRequest($input);
$package_request->user_id = \Auth::id();
$package_request->status = 'waiting';
if(!$package_request->save())
return Response::error(__('errors.save_data', ['attr' => __('errors.attribures.package_request')]));
return Response::success(PackageRequest::find($package_request->id), 201);
}
The mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the manipulated value on the Eloquent model's internal $attributes property.
The mutator will be automatically called when you attempt to set the value to attribute tracking_code, and you are not setting anything to it. What you need is setting default value to tracking_code before creating, so you can add these codes in model PackageRequest:
protected static function boot()
{
parent::boot();
static::creating(function($package_request)
{
$package_request->tracking_code = mt_rand(1000000, 9999999);
});
}
I have a Printer model which has a page_count field..
the user will be able to input the current page_count...
the new page_count must be greater than the existing data in the database... How can I do that?
I had the same issue solved like this, though someone already gave the solution in the comments section.
/**
* #param array $data
* validates and Stores the application data
*
*/
public function sendMoney(Request $request)
{
//get the value to be validated against
$balance = Auth::user()->balance;
$validator = Validator::make($request->all(), [
'send_to_address' => 'required',
'amount_to_send' => 'required|max:'.$balance.'|min:0.01|numeric',
]);
//some logic goes here
}
Depending on your use case you could modify...
Happy Coding
Assuming you have Printer model which contains the page_count column.
You can define a custom validation rule in your AppServiceProvider's boot() method.
public function boot()
{
//your other code
Validator::extend('page_count', function($attribute, $value, $parameters, $validator) {
$page_count = Printer::find(1)->first()->value('page_count'); //replace this with your method of getting page count.
//If it depends on any extra parameter you can pass it as a parameter in the validation rule and extract it here using $parameter variable.
return $value >= $page_count;
});
//your other code
}
Then, you can use it in your validation rule like below
'page_count' => 'required|page_count'
Reference: Laravel Custom Validation
I trying to post form in yii but don't have any idea regarding validation, i go through some yii documentation but not getting it. can't we do validation without form object of yii? means in view i am using normal HTML for form of yii.
Validation is built into Yii in Models, whether it is Form models or CActiveRecord models.
In order to implement validation, place the validation rules in your model. In the example below, I am using an activerecord model.
class Customer extends CActiveRecord {
// :
public function rules(){
return array(
array('name, surname, email', 'required'),
array('age, email', 'length','min'=>18)
);
}
You can now validate ANY form, whether you are using Yii forms or plain HTML forms.
To enforce validation, your controller must populate the model values, then call upon the model to check the data against the rules you defined earlier.
class CustomerController extends CController {
// :
$customerModel = new Customer;
// Set fields using this format ...
$customerModel->attributes['name'] = $_FORM['user'];
// ...or this ...
$customerModel->age = $_FORM['age'];
// ...of this
$customerModel->setEmail($_FORM['email'];
// Now validate the model
if ($customerModel->validate) {
return true;
}
else {
return false;
}
// :
}
}
In action, you need to add
$this->performAjaxValidation($model);
in _form, add
'enableAjaxValidation'=>true,
and in model, you need to set rules,
public function rules(){
return array(
// array('username, email', 'required'), // Remove these fields from required!!
array('email', 'email'),
array('username, email', 'my_equired'), // do it below any validation of username and email field
);
}
I think, this will be helpful for you.
I've created a widget with a CActiveForm in it. Everything works ok, but now i want to enable ajax validation for it.
The problem is that the output of my ajax validation is containing, besides the validation JSON string, all (well a part of it, since Yii::app()->end() stops the rest) of my html as well. Not weird, because i'm using it within a widget and the validation request is done to the controller/action where i've placed this widget on.
Is there some way to prevent outputting all the html, so a valid JSON string is returned?
I've already tried to set the validationUrl in the CActiveForm to another controller/action, but the problem is that i have to send the model with it and this model is determined in my widget and not on the validationUrl.
Widget:
public function run()
{
$model = new User;
$model->scenario = 'create';
$this->performAjaxValidation($model);
if (isset($_POST['User'])) {
$model->attributes = $_POST['User'];
if ($model->save()) {
}
}
$this->render('register-form', array(
'model' => $model
));
}
/**
* Performs the AJAX validation.
* #param User $model the model to be validated
*/
protected function performAjaxValidation($model)
{
if(isset($_POST['ajax']))
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}
Output of performAjaxValidation() (the ajax call):
.. more html here ..
<section class="box">
<h1>Register form simple</h1>
{"UserPartialSignup_email":["Email is geen geldig emailadres."]}
I solved it this way:
I've created an AJAX controller where the validation is done:
AjaxController:
/**
* Validates a model.
*
* Validates a model, POST containing the data. This method is usually used for widget based forms.
*
* #param $m model name which have to be validated
* #param $s scenario for this model, optional.
* #return string JSON containing the validation data
*/
public function actionValidate($m, $s = null)
{
if ($this->checkValidationData($m, $s) && isset($_POST['ajax']))
{
$model = new $m;
$model->scenario = $s;
echo CActiveForm::validate($model);
Yii::app()->end();
} else {
throw new CHttpException(500, 'No valid validation combination used');
}
}
You can give the model name and a scenario as GET parameters with it, i'm checking if this combination is allowed by the checkValidationData() method.
In the view of my widget where the CActiveForm is placed, i've added the validationUrl, referring to ajax/validate:
widgets/views/registerform.php:
<?php $form = $this->beginWidget('CActiveForm', array(
'id'=>'signup-form-advanced',
'enableAjaxValidation'=>true,
'clientOptions' => array(
'validationUrl' => array('ajax/validate', 'm' => get_class($model), 's' => 'create')
)
//'enableClientValidation'=>true,
)); ?>
I would like a best practice for this kind of problem
I have items, categories and category_item table for a many to many relationship
I have 2 models with these validations rules
class Category extends Basemodel {
public static $rules = array(
'name' => 'required|min:2|max:255'
);
....
class Item extends BaseModel {
public static $rules = array(
'title' => 'required|min:5|max:255',
'content' => 'required'
);
....
class Basemodel extends Eloquent{
public static function validate($data){
return Validator::make($data, static::$rules);
}
}
I don't know how to validate these 2 sets of rules from only one form with category, title and content fields.
For the moment I just have a validation for the item but I don't know what's the best to do:
create a new set of rules in my controller -> but it seems redundant
sequentially validate Item then category -> but I don't know how to handle validations errors, do I have to merges them? and how?
a 3rd solution I'm unaware of
here is my ItemsController#store method
/**
* Store a newly created item in storage.
*
* #return Redirect
*/
public function store()
{
$validation= Item::validate(Input::all());
if($validation->passes()){
$new_recipe = new Item();
$new_recipe->title = Input::get('title');
$new_recipe->content = Input::get('content');
$new_recipe->creator_id = Auth::user()->id;
$new_recipe->save();
return Redirect::route('home')
->with('message','your item has been added');
}
else{
return Redirect::route('items.create')->withErrors($validation)->withInput();
}
}
I am very interested on some clue about this subject
thanks
One way, as you pointed yourself, is to validate it sequentially:
/**
* Store a newly created item in storage.
*
* #return Redirect
*/
public function store()
{
$itemValidation = Item::validate(Input::all());
$categoryValidation = Category::validate(Input::all());
if($itemValidation->passes() and $categoryValidation->passes()){
$new_recipe = new Item();
$new_recipe->title = Input::get('title');
$new_recipe->content = Input::get('content');
$new_recipe->creator_id = Auth::user()->id;
$new_recipe->save();
return Redirect::route('home')
->with('message','your item has been added');
}
else{
return Redirect::route('items.create')
->with('errors', array_merge_recursive(
$itemValidation->messages()->toArray(),
$categoryValidation->messages()->toArray()
)
)
->withInput();
}
}
The other way would be to create something like an Item Repository (domain) to orchestrate your items and categories (models) and use a Validation Service (that you'll need to create too) to validate your forms.
Chris Fidao book, Implementing Laravel, explains that wonderfully.
You can also use this:
$validationMessages =
array_merge_recursive(
$itemValidation->messages()->toArray(),
$categoryValidation->messages()->toArray());
return Redirect::back()->withErrors($validationMessages)->withInput();
and call it in the same way.
$validateUser = Validator::make(Input::all(), User::$rules);
$validateRole = Validator::make(Input::all(), Role::$rules);
if ($validateUser->fails() OR $validateRole->fails()) :
$validationMessages = array_merge_recursive($validateUser->messages()->toArray(), $validateRole->messages()->toArray());
return Redirect::back()->withErrors($validationMessages)->withInput();
endif;