Is it possible to create customize rule for a model in Yii2 with
name of action and its parameter.
I know that rules in model can create by on action.
Because I have a model and controller like this :
Model
public function rules()
{
$rules = [
[['pre_approved_by', 'pre_approved_date'], 'required', 'on' => 'pre'],
[['first_approved_by', 'first_approved_date'], 'required', 'on' => 'first'],
];
return $rules;
}
Controller
public function acctionApprove($hierarchy, $id){
$model = $this->findModel($id);
if($hierarchy != 1){
$model->scenario = "pre";
}else{
$model->scenario = "first";
}
}
Please advise
You can create a conditional validation. With this kind of rule you can write your own function.
For example:
['state', 'required', 'when' => function($model) {
return $model->country == 'USA';
}]
For a detailed explaination, check out the official doc
Related
I use Laravel 8 with Resource to define routes in api.php and DestroyProductRequest in my controller:
Route::resources([
'product' => ProductController::class,
]);
In the controller:
public function destroy(DestroyProductRequest $request, Product $product) {
$product->delete();
return Response::HTTP_OK;
}
In the DestroyProductRequest.php:
public function rules() {
return [
'id' => 'required|integer|exists:products,id',
];
}
Now the key is the Route::resource convert the incoming id from the url into a model. But now how can I write the correct rule in the the rules() function?
Now it sais id: ["The id field is required."] in the error response.
Any idea?
By default Laravel does not validate the route parameters.
You have add custom logic in order to work
public function rules()
{
return [
'product' => 'required|integer|exists:products,id',
];
}
protected function validationData()
{
// Add the Route parameters to you data under validation
return array_merge($this->all(),$this->route()->parameters());
}
In a normal use case, you wouldn't validate route request. Laravel would return a 404 Not Found exception. Which is a standard and appropriate response.
Since you are using the resource, the route parameter is implicit biding key (product id), So Laravel bind the passed id as product implicitly. Change your Rules code segment as following,
public function rules() {
return [
'product' => 'required|integer|exists:products,id',
];
}
public function destroy(Product $product) {
$product->delete();
return Response::HTTP_OK;
}
If you wanna add ACL
if ($user->can('destroy', $product)) {
$product->delete();
return Response::HTTP_OK;
}
use Validator for your question
use Illuminate\Support\Facades\Validator;
public function destroy(DestroyProductRequest $request, Product $product) {
$rules = [
'id' => 'required|integer|exists:products,id',
];
$messages = [
'id.required' => 'The id field is required.',
'id.integer' => 'The id field need to be integer.',
'id.exists' => 'The id field is exists.',
];
Validator::make($request->all(), $rules, $messages)->validate();
$product->delete();
return Response::HTTP_OK;
}
Read more here: https://laravel.com/docs/8.x/validation
I have custom validation for validating data. The custom validation doesn't have unique rule as I need to ignore this on update, therefore I am using unique rule on store() method. But this is ignored, and it only works if I change the custom validation with default validation.
It works if I have the following:
public function store(Request $request)
{
if (!$this->user instanceof Employee) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$request->validate([
'name' => 'required|max:50|unique:centers'
]);
$center = Center::create($request->all());
return response()->json($center, 201);
}
But this doesn't work if I change the method signature to the following:
public function store(CustomValidation $request)
How can I use both together? I do not want to move the custom validation code inside the method as I have to repeat msyelf for update method then.
I think it will help you
use Illuminate\Contracts\Validation\Rule;
class CowbellValidationRule implements Rule
{
public function passes($attribute, $value)
{
return $value > 10;
}
public function message()
{
return ':attribute needs more cowbell!';
}
}
and
public function store()
{
// Validation message would be "song needs more cowbell!"
$this->validate(request(), [
'song' => [new CowbellValidationRule]
]);
}
or
public function store()
{
$this->validate(request(), [
'song' => [function ($attribute, $value, $fail) {
if ($value <= 10) {
$fail(':attribute needs more cowbell!');
}
}]
]);
}
I have a yii2 form which contain a checkbox list items which i made like this:
<?php $CheckList = ["users" => 'Users', "attendance" => 'Attendance', "leave" => 'Leave', "payroll" => 'Payroll'];?>
<?= $form->field($model, 'MenuID')->checkboxList($CheckList,['separator'=>'<br/>']) ?>
Now what i need is to save the values in the database column as a comma separated value.
I tried to modify the create function in my controller in this way:
public function actionCreate()
{
$model = new Role();
if ($model->load(Yii::$app->request->post())) {
if ($model->MenuID != " ") {
$model->MenuID = implode(",", $model->MenuID);
}
$model->save();
return $this->redirect(['view', 'id' => $model->RoleID]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
But the values are not being saved in the database
You need to set your model rules().
When you call $model->load(Yii::$app->request->post()); the framework call method setAttributes() with param $safeOnly = true. This method with param $safe = true check if attributes are safe or not according to the rules of model. If you haven't any rules on the model all attributes are considered unsafe so your model is not populated.
Add rules() on your model and your code works
class Role extends yii\db\ActiveRecord
{
...
public function rules()
{
return [
['MenuID', 'your-validation-rule'],
];
}
...
Some additional info
N.B. If you do not specify scenario in the rules the default scenario is 'default' and if during instantiate of model object you set scenario to another didn't work. My example:
You have the same rules as I wrote before and you run this code
...
$model = new Role(['scenario' => 'insert']);
if ($model->load(Yii::$app->request->post())) {
...
model is empty after load becouse any rules is founded in 'insert' scenario and your problem is back. So if you want a rule that work only in particular scenario you must add 'on' rules definition. Like this:
...
public function rules()
{
return [
['MenuID', 'your-validation-rule', 'on' => 'insert'],
];
}
...
For more example and explanations visit:
Declaring Rules
load()
setAttributes()
safeAttributes()
I have tried different approach for create action.
Usually i have one action which renders, validates and saves data.
Now i want two separate actions. One for rendering view and second for validation and data storage.
View
$form = ActiveForm::begin([
'action' => ['ew/eshop-create'],
'method' => 'post',
]);
echo $form->field($model, 'input')->textarea([
'rows' => '20'
]);
echo Html::submitButton(
'<i class="glyphicon glyphicon-send"></i> OdoslaƄ',
[
'class' => 'btn btn-success',
'name' => 'create-button'
]
);
ActiveForm::end();
Model
class EshopCreate extends Model
{
public $input;
public function attributeLabels()
{
return [
'input' => 'JSON vstup'
];
}
public function rules()
{
return [
['input', 'required'],
['input', 'validateInput'],
];
}
public function validateInput()
{
// validate json
$this->addError('input', 'Something is wrong');
}
}
Controller
class EwController extends Controller
{
public function actionEshopCreateForm()
{
$model = new EshopCreate();
return $this->render('eshop-create-form', [
'model' => $model
]);
}
public function actionEshopCreate()
{
$model = new EshopCreate();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
exit('create');
}
return $this->redirect(['ew/eshop-create-form']);
}
}
Edit:
So i had problem with validation. I switched model->load and model->validate in actionEshopCreate.
So it works fine, but message from validateInput is not displayed. Also when i turn off clientvalidation, there are no error messages at all. So my question is how to pass errors from one action to another.
Thanks.
You are redirecting to a different action when the form is submitted and fail to pass the validation
return $this->redirect(['ew/eshop-create-form']);
the EshopCreate model will lose all validation messages when the redirection happens
Probably you want to do something like this
class EwController extends Controller
{
public function actionEshopCreateForm()
{
$model = new EshopCreate();
return $this->render('eshop-create-form', [
'model' => $model
]);
}
public function actionEshopCreate()
{
$model = new EshopCreate();
if ($model->load(Yii::$app->request->post()) && $model->validate())
{
//store the model data in session or somewhere for example where you can retrieve it later in the actionEshopCreateForm() action
return $this->redirect(['ew/eshop-create-form']);
}
return $this->render('eshop-create-form', [
'model' => $model
]);
}
}
I have a model and validation rules for it:
class User extends ActiveRecord implements IdentityInterface
{
...
public function rules()
{
return [
[['username', 'password', 'email'], 'required', 'on' => 'insert'],
[['password', 'email'], 'required', 'on' => 'update'],
]
}
Actually the code produces no validators. When I remove 'on' section, everything goes well.
Digging in official documentation and search thru The Web didn't help me to understand what is the issue, and why can't I have custom required fields sets for different actions.
The Scenario is not automaticaly setted by Yii2 ActiveReccoed. If you need a specific scenario you must create it and assign
E.g. for update ...
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['update'] = ['password', 'email'];//Scenario Values Only Accepted
return $scenarios;
}
Also you can set scenario in your actionUpdate
public function actionUpdate($id)
{
$model = $this->findModel($id);
$model->scenario = 'update';
........
}