Yii Validate an un-bound Variable (non-stored) - php

Classic problem:
verify that a user accepted the contract terms but the value of the acceptance is not stored (bound) in the database...
Extend CFormModel rather than CActiveForm (because CActiveForm binds
values to DB)
Post a CFormModel to a controller action
Validate a CFormModel
I'm asking this question to answer it because the existing questions end in see the documentation...

extend CFormModle, define the rules and got to validate. With bound variables you validated as part of save. Now you validate() by itself but Validate requires a list of attributes which is not defined in CFormModel. So, what do you do? You do this:
$contract->validate($contract->attributeNames())
Here's the full example:
class Contract extends CFormModel
{
...
public $agree = false;
...
public function rules()
{
return array(
array('agree', 'required', 'requiredValue' => 1, 'message' => 'You must accept term to use our service'),
);
}
public function attributeLabels()
{
return array(
'agree'=>' I accept the contract terms'
);
}
}
Then in the controller you do this:
public function actionAgree(){
$contract = new Contract;
if(isset($_POST['Contract'])){
//$contract->attributes=$_POST['Contract']; //contract attributes not defined in CFormModel
...
$contract->agree = $_POST['Contract']['agree'];
...
}
if(!$contract->validate($contract->attributeNames())){
//re-render the form here and it will show up with validation errors marked!
}
The results:

Related

Newbie question about controller and request in Laravel in my case

I am new in Laravel, what I try to achieve is very simple thing, I would like to use FormRequest provided by Laravel to do validation of the request, but I encounter some puzzles (which I am sure is easy things to solve if you are experienced in Laravel).
Here is what I tried:
I have route maps to controller:
Route::put('user/{name}', 'UserController#show');
I can get the name parameter in show function:
class UserController {
public function show($name)
{
// validtion rules to apply
...
}
}
I have validation rules to apply to the request, so I decided to create form request by php artisan make:request ShowRequest, which creates the form request class:
class ShowRequest extends FormRequest {
public function authorize()
{
return true;
}
public function rules()
{
return [
// my validation rules here
];
}
}
Since I have above request class, so I refactored the show function in controller to receive the ShowRequest .
class UserController {
public function show(ShowRequest $request)
{
// now I don't need validtion rules in this function
// but how can I access the 'name' parameter now
...
}
}
I have two questions to ask:
Inside the refactored show function, how can I now access the route parameter name ?
If we forget about the parameter is a name (please don't focus on what to validate for name, imaging it is an object or value to validate in general). How to add custom logic for handling validation error instead of using Laravel default behaviour. I want to inject code like dummy code below:
if (!$validator->pass())
{
//my custom code for handling validation failure
}
Where to put my custom code for handling validation error now? I mean I don't know where to have this logic, in controller? in the request class? how?
You still can add the parameter $name in the show() method of your controller as it's part of the routed url more than the validated form/data. (recommanded)
class UserController {
public function show(ShowRequest $request, $name)
{
//...
}
}
You can also access it from the request object
class UserController {
public function show(ShowRequest $request)
{
$request->input('name');
}
}
As for the error messages (not the exception) you can add the messages() method to your ShowRequest::class
class ShowRequest extends FormRequest
{
/**
* #return array
*/
public function messages()
{
return [
'name.required' => 'The name is required',
'name.numeric' => 'The name must be a number',
//...
];
}
}
If you instead need to validate that the name catched by the route is only composed of letter OR really exists as a field in your DB (like a slug of a post) you need to add some validation in your route declaration.
Setup a route that catches request only if it is composed of letters.
Route::get('/user/{name}', 'Controller#show')->where(['name' => '[a-z]+']);
Setup a route that catches request only if the "name" exists in DB:
User.php
Class User //..
{
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'name';
}
}
web.php
//
Route::get('/user/{user:name}', 'Controller#show');
And adapt your controller to take a user directly
class UserController {
public function show(ShowRequest $request, User $user)
{
//...
}
}
You can access the values of the Form Request using this
$validated = $request->validated();
The $validated will have all the values which had been validated by the FormRequest.
To answer your second question, if you want to throw custom validation, you can always use the following
throw ValidationException::withMessages(['name' => 'Something is wrong']);

Custom validation in yii2 while merging two public variables

Actually I'm working on ajax validation in Yii 2. I am sending two public variables data into a column in DB. while loading a post values. How to validate custom onto that field.
My Code:
public $prefix;
public $mobile;
$model->phone = Yii::$app->request->post('prefix') . '' . Yii::$app->request->post('mobile');
and I want this
['phone, 'unique']
Thanks in advance
Add the rule to the class definition of the model, validate and do with the validation result what you want. E.g. return true when validated true or the error message when validated false.
class YourModel extends ActiveRecord {
public function rules()
{
return [
['phone', 'unique'],
];
}
}
$model->validate();
If you have more validation rules you can get the result for the phone attribute using $model->getErrors('phone').
You need to merge both variables before calling validate function on model.
Your controller action code should be like below :
$model->phone=model->prefix.$model->mobile;
$model->validate();
//rest of code

How can I chain multiple form validation requests together?

In a Laravel project I'm working on, I'm wanting to create an API. In that API there will be certain JSON keys that will be required in every request. E.g. a token, or other fields that are ALWAYS required. I am familiar with Laravel's form request feature which allows you to easily create a class with a rules method containing an array of validation logic. However, I am wanting to know if there is a way where I can make one Request class that handles the "always required" fields and then bolt on another request class containing specific field validation for that endpoint.
E.g.
// MasterRequest.php
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
// ProductRequest.php
public function rules() {
return [
'product_id' => 'required|integer',
];
}
And then some way always call MasterRequest validation on EVERY api route, and then specify the type of request validation for each route's unique needs?
Is this doable, or even the correct approach?
This is fairly easy to set up, use OOP properties of PHP.
The easiest way (and obvious one):
Make yourself "master class for always required fields" you can also declare it as abstract class.
File AlwaysRequired.php
abstract class AlwaysRequired extends FormRequest
{
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
}
and ProductRequest.php
class ProductRequest extends AlwaysRequired
{
public function rules() {
return array_merge(parent::rules(),
['product_id' => 'required|integer']);
}
}
Array merge on php.net
The property way:
Make yourself master class in which you will declare property with "always required" rules and then just array_merge(array,...) it in child class (just like example above).
The "hardest" and most confusing way, but fully automatic:
You can leverage magic functions and method/property visibility of PHP language.
Again make yourself a master class in which you will have a protected property with rules array and implementation for __call() magic method.
Note: You can test code below in interactive shell of PHP $php -a and copy-paste the code.
abstract class A { // Master class
protected $rules = ['abc' => 'required'];
function __call($name, $arg) {
if(method_exists($this, 'rules')){
return array_merge($this->rules, $this->rules());
} else {
//or handle any other method here...
die(var_dump($name, $arg));
}
}
}
class B extends A { //Generic class just like ProductRequest...
protected function rules() { // function must be declared as protected! So its invisible for outside world.
return ['def' => 'required'];
}
}
$b = new B();
var_dump($b->rules());
How it works?
Laravel behind the scenes tries to run rules() method on the request class you specify (in your case ProductRequest), declaring it as protected means that it can not be called except from itself or by another child, which means that __call() method is called instead which is declared in abstract parent class. __call() method simply identifies if caller wanted to call non-existent (because of protected visibility is set) method rules() if that is so it merges the child's rules() result with $rules and returns it.
Checking for correct API key should be handled in Middleware.
You can manually execute Request classes one by one:
public function store()
{
try{
app(MasterRequest::class);
} finally {
app(ProductRequest::class);
}
/*... */
}

Passing properties from a child object to the parent PHP

I've worked with cakePHP in the past and liked the way they built their model system. I want to incorporate their idea of handling validation between extended models.
Here is an example:
class users extends model {
var $validation = array(
"username" => array(
"rule" => "not_empty"
),
"password" => array(
"rule" => "valid_password"
)
);
public function create_user() {
if($this->insert() == true) {
return true;
}
}
}
class model {
public function insert() {
if(isset($this->validation)) {
// Do some validation checks before we insert the value in the database
}
// Continue with the insert in the database
}
}
The problem with the this is that model has no way of getting the validation rules as it's the parent class. Is there a way I can pass the $validation property to the parent class without explicitely passing the validation rules through say the create_user() method as a parameter?
EDIT:
Also, avoiding passing it via the __construct() method to the parent class. Is there another way of doing this which would not cause a lot of extra code within my users class but get the model class to do most of the work (if not all?)
If the instance is a $user, you can simply refer to $this->validation in model::insert().
It would seem that model should also be abstract in this case, preventing instantiation and perhaps confusion.
Create a new abstract method in the model class named: isValid() that each derived class will have to implement, then call that method during the insert() function.
model class:
class model {
abstract protected function isValid();
public function insert() {
if($this->isValid())) { // calls concrete validation function
}
// Continue with the insert in the database
}
}
user class:
class users extends model {
var $validation = array(
"username" => array(
"rule" => "not_empty"
),
"password" => array(
"rule" => "valid_password"
)
);
protected function isValid() {
// perform validation here
foreach ($this->validation) { //return false once failed }
return true;
}
public function create_user() {
if($this->insert() == true) {
return true;
}
}
}

different rules for the same AR model in Yii

I have one model extending AR class with specific rules. But now i need to insert row into this table, but with other rules. Is i need to create other model with new rules, or it is possible to define orther rules?
You can set validation scenario. For example:
$model = new Post();
$model->scenario = 'new_line';
$model->attributes = $_GET['data'];
if ($model->validate()){
$model->save(false);
}
in your model:
public function rules()
{
return array(
array('username, text', 'required','on' => 'new_line')
);
}
In model rules all array lines must have key "on", else this rules will not apply.
Read more here.
If you are extending your class (active records) then you can actually just override your rules() function i.e.:
class User extends ActiveRecord(){
function rules(){
return array(array(
// Nomrally a rule
))
}
}
And then make your next class:
class User_extended extends ActiveRecord(){
function rules(){
return array(array(
// Nomrally a rule
))
}
}
And that should be it. You can then call the User_extended class and your rules will apply to the parent User class since Yii grabs the rules in a $this context and $this will be your child class.
But you can also use scenarios here, but it might get dirty especially if you need to override other methods.
thx. Now i'm trying to use this
/**
* #param string $attribute fields names wich should be validated
* #param array $params additional params for validation
*/
public function ValidatorName($attribute,$params) { … }

Categories