I have considered defining validation rules for different parts of my application in a class as static variables. For example,
$VALIDATION_RULES = [
'signatures' => [
'first_name' => ['required', 'max:255', 'required','string'],
'last_name' => ['required', 'max:255', 'required', 'string'],
'enabled' => [ 'required', Rule::in(['on', 'off']) ],
'signature_file' => ['required', 'mimes:png,jpeg,jpg', 'max:1024'],
'operator_id' => ['required', 'numeric'],
],
];
However, I get this error.
Symfony\Component\ErrorHandler\Error\FatalError
Constant expression contains invalid operations
This is due to
Like any other PHP static variable, static properties may only be
initialized using a literal or constant before PHP 5.6; expressions
are not allowed. In PHP 5.6 and later, the same rules apply as const
expressions: some limited expressions are possible, provided they can
be evaluated at compile time.
according to the php official documentation.
What is the correct way to do this?
P.S.: This variable is defined inside a class as a public and static member. In the code, wherever I need to validate an input, I can call
$validated_input = $request->validate($VALIDATION_RULES['signatures']);
This way, I can have all the rules centralized in one class.
If you want to have a part of a code and use it in the other FormRequests too, you can use traits.
Suppose you want to validate users in the multi FormRequests:
Create UserRequestTrait and add this method to the trait:
private function userInfoValidator(string $prefix = ''): array
{
return [
$prefix . 'first_name' => ['required', 'string', 'max:100'],
$prefix . 'last_name' => ['required', 'string', 'max:100'],
];
}
Now you can use it in the FormRequest classes:
class UserFormRequest extends FormRequest
{
use UserRequestTrait;
public function rules()
{
$rules = [
// Add the other rules
];
// validate user info
$userValidation = $this->userInfoValidator();
return array_merge(
$rules,
$userValidation
);
}
}
And if you are validating an object, you can add the prefix in the userInfoValidator method, like this:
$userValidation = $this->userInfoValidator('users.*.');
You cannot use functions ,expressions,objects while declaring class properties (includes static and non-static property).which means you cannot call someFunction() or new Sampleclass inside the property, you can only use static values. You'll have to add those values differently inside the constructor or maybe some other method.
so By removing Rule::in(['on', 'off']) inside the property (which is object) will fix the issue
class MyTestClass {
public $VALIDATION_RULES = [
'signatures' => [
'first_name' => ['required', 'max:255', 'required','string'],
'last_name' => ['required', 'max:255', 'required', 'string'],
'enabled' => [ 'required'],
'signature_file' => ['required', 'mimes:png,jpeg,jpg', 'max:1024'],
'operator_id' => ['required', 'numeric'],
],
];
}
$obj = new MyTestClass;
dd($obj);
You can read the docs
https://php-legacy-docs.zend.com/manual/php5/en/language.oop5.static
Related
I created a trait to use with a controller. The controller should start the trait function, which validates its input and then does a thing.
Inside the FooController.php:
[..]
$do_stuff = $this->create_stuff($input);
The trait:
<?php
namespace App\Traits;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
[..]
use Illuminate\Support\Facades\Validator;
trait Foo
{
public function create_stuff(Request $input)
{
// validation part
$validatedData = $input->validate([
'Value' => 'required|numeric',
]);
// end of validation part
[..]
Everything works fine without the validation part but as soon as I add it I get the error:
Argument 1 passed to App\\Http\\Controllers\\FooController::create_stuff() must be an instance of Illuminate\\Http\\Request, instance of stdClass given,[..]
I understand the $input is a 'normal' (?) PHP object and it seems the validation only works with 'request' objects, so how can I make this work?
First of all please don't create a trait for validation create a request for validation using PHP artisan
PHP artisan make:request Your_request_name
and inside your request add your validations like this
public function rules()
{
return [
'sponsor_name' => ['required', 'string'],
'sponsor_type' => ['required', 'string'],
'mobile' => ['required', 'string'],
'email' => ['required', 'string'],
'nationality' => ['required', 'string'],
'sponsor_issued_from' => ['required', 'string'],
'sponsor_issued_date' => ['required', 'date'],
'sponsor_address' => ['required', 'string'],
];
}
public function authorize()
{
return true;
}
then inside your request add your validations
after that just call your request in your controller function
for example
public function (Your_request_name $request)
{
...
}
ProductController
public function store()
{
$product = Product::create($this->validateRequest());
return redirect('/product');
}
public function update(Product $product)
{
$product->update($this->validateRequest());
return redirect('/product');
}
private function validateRequest()
{
return request()->validate([
'sub_category_id' => ['required'],
'name' => ['required', 'min:4'],
'code' => ['required', 'alpha_dash','unique:products'],
'description' => ['required', 'min:4'],
'color' => ['required', 'min:3'],
'price' => ['required', 'integer'],
]);
}
here code has unique value from table products. but whenever I edit the form it says code has already taken. so how to execute this without edit the 'code'(its unique).
You can ignore certain ids during the unique check:
use Illuminate\Validation\Rule;
public function store()
{
Product::create($this->validateRequest(new Product()));
return redirect('/product');
}
public function update(Product $product)
{
$product->update($this->validateRequest($product));
return redirect('/product');
}
private function validateRequest(Product $product)
{
return request()->validate([
'sub_category_id' => ['required'],
'name' => ['required', 'min:4'],
'code' => ['required', 'alpha_dash', Rule::unique('products')->ignore($product)],
'description' => ['required', 'min:4'],
'color' => ['required', 'min:3'],
'price' => ['required', 'integer'],
]);
}
Here you either pass the existing model when updating or a new model instance when storing, so the call to $product->id either returns null when storing a new product so no product in the database is ignored, or the id when updating and then only that product is ignored.
From the docs:
Forcing A Unique Rule To Ignore A Given ID:
Sometimes, you may wish to ignore a given ID during the unique check.
For example, consider an "update profile" screen that includes the
user's name, e-mail address, and location. You will probably want to
verify that the e-mail address is unique. However, if the user only
changes the name field and not the e-mail field, you do not want a
validation error to be thrown because the user is already the owner of
the e-mail address.
To instruct the validator to ignore the user's ID, we'll use the Rule
class to fluently define the rule. In this example, we'll also specify
the validation rules as an array instead of using the | character to
delimit the rules:
use Illuminate\Validation\Rule;
Validator::make($data, [
'email' => [
'required',
Rule::unique('users')->ignore($user->id),
],
]);
Be aware of the following though:
You should never pass any user controlled request input into the
ignore method. Instead, you should only pass a system generated unique
ID such as an auto-incrementing ID or UUID from an Eloquent model
instance. Otherwise, your application will be vulnerable to an SQL
injection attack.
So the problem here is you are using the same rule for creating the product and updating the product.
If you are using the latest Laravel, you may want to read the documentation about form request https://laravel.com/docs/7.x/validation#form-request-validation and create different form request for store and update.
If you still want to use your way, you can try as below
private function validateRequest()
{
$rules = [
'sub_category_id' => ['required'],
'name' => ['required', 'min:4'],
'description' => ['required', 'min:4'],
'color' => ['required', 'min:3'],
'price' => ['required', 'integer'],
];
if (request()->isMethod('store')) {
$rules['code'] = ['required', 'alpha_dash'];
}
return request()->validate($rules);
}
Please tell me whether it works!
Try this, proper and clean code
ProductController
use Illuminate\Validation\Rule; // add this
public function store()
{
$product = Product::create($this->validateRequest());
return redirect('/product'); //use route instead of URL
}
public function update(Product $product)
{
$product->update($this->validateRequest($product->id));
return redirect('/product'); //use route instead of URL
}
private function validateRequest($id = null)
{
return request()->validate([
'sub_category_id' => 'required',
'name' => 'required|min:4',
'code' => 'required|alpha_dash|' . Rule::unique('products')->ignore($id),
'description' => 'required|min:4',
'color' => 'required|min:3',
'price' => 'required|integer',
]);
}
Suggest to use Form request validation
I'm trying to block users from using certain names like 'admin' or 'operator'.
I've tried fiddling with both Controllers/Auth/RegisterController and Controller/RegisterController but failed.
What I've tried was something like this:
in Controllers/Auth/RegisterController,
if ($data['name'] === 'admin' || $data['name'] === 'operator') {
return redirect()->back()->withErrors(['Invalid username']);
}
else {
session()->flash('message', 'Welcome!');
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
The code above gave me this error:
Argument 1 passed to Illuminate\Auth\SessionGuard::login() must be an
instance of Illuminate\Contracts\Auth\Authenticatable, string given,
called in /var/www/vendor/laravel/ui/auth-backend/RegistersUsers.php
on line 36
I've also searched whether validators can block specific words but failed.
I know I can work this around by using JS, but I think Laravel would have some function like this.
Go to RegisterController.php.
There will be a validator(array $data) function that validates your registration input.
Add not_in validation check for the name field.
Something like this:
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255', 'not_in:admin,operator'],
'pan' => ['required', 'string', 'min:10', 'max:10', 'unique:users,username'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
Hopefully, this helps.
You can try to validate if requested name is not in the list of predefined blocked names:
use Illuminate\Validation\Rule;
Validator::make($data, [
'toppings' => [
'required',
Rule::notIn(['admin', 'superuser']),//etc..
],
]);
Hope it helps.
Here's what I did in Laravel 9 with the Breeze scaffolding.
I created a separate "Services" trait with the reserved usernames, so that I can easily implement and extend it.
Create a Services folder at app/Services
Create a trait Reserved at app/Services/Reserved.php
Reserved.php
<?php
namespace App\Services;
trait Reserved
{
public static function usernames()
{
return ['admin', 'operator', 'someBadWord'];
}
}
Next open app/Http/Controllers/Auth/RegisteredUserController.php and import the trait, then add it.
Now just add it to your validation rules using the notIn rule.
Rule::notIn(Reserved::usernames())
RegisteredUserController.php
use Illuminate\Validation\Rule;
use App\Services\Reserved;
class RegisteredUserController extends Controller
{
use Reserved;
...
public function store(Request $request)
{
$request->validate([
'username' => ['required', 'string', 'max:16', 'unique:users', Rule::notIn(Reserved::usernames())],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
...
}
}
Create RegisterControllerRequest using the following command:
php artisan make:request RegisterControllerRequest
find out your recent created file in app/Http/Requests, then update rules() to be:
public function rules()
{
return [
'name' => 'required|notIn:admin,operator',
];
}
Then update your controller to use the new validation:
After name space add:
use App\Http\Requests\RegisterControllerRequest;
And finally inject the validation as a parameter in your register method:
public function register (RegisterControllerRequest $request)
For more information check documentation: https://laravel.com/docs/7.x/validation#form-request-validation
I'm trying to use a validation on 'name' as unique, but to be ignored if the objected that has that same name is the same object being updated.
The Validation keeps failing, can you help me figuring out why?
Validator Function - I'm using same validator function to both create and update and only need to apply the rule on the update.
protected function validator(array $data, Wharehouse $wharehouse = null)
{
//different validations for create and edit
if($wharehouse != null){
return Validator::make($data, [
'name' => ['required', 'string', 'max:255', Rule::unique('wharehouses')->ignore($wharehouse)],
'espacoTotal' => ['required', 'numeric', 'max:60000']
]);
}else{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255', Rule::unique('wharehouses')],
'espacoTotal' => ['required', 'numeric', 'max:60000']
]);
}
}
Validation Call
protected function editById(Request $request)
{
$wharehouse = Wharehouse::find($request->wharehouse_id);
$validation = $this->validator($request->all(),$wharehouse);
if ($validation->fails()) {
return Redirect::to('/wharehouses')->withInput()->withErrors($validation);
} else {
$wharehouse->name = $request->input('name');
$wharehouse->espacoTotal = $request->input('espaco');
$wharehouse->save();
return back()->with('create.success','Armazem actualizado com sucesso.');
}
}
Append the id of the instance currently being updated to the validator.
Pass the id of your instance to ignore the unique validator.
In the validator, use a parameter to detect if you are updating or
creating the resource.
If updating, force the unique rule to ignore a given id: unique:table,column,except,idColumn
//rules
'name' => ['required', 'string', 'max:255', 'unique:users,name,' . $userId],
for some reason that I do not know, when I try to pass a validation without request and try to use one if
public function save(Request $request){
$request = $request->validate([
'name' => ['string', 'max:255'],
'email' => ['string', 'email', 'max:255', 'unique:users'],
]);
if($request->name != null){
return $request;
}
return $request;
}
You are replacing the type of your $request with the result from validate()
The validation will handle the what you wish to, so no need to worry. If you say the variable name is required, it will enforce it to not be null or empty;
So, just replace the result to a specific variable instead of replacing $request by doing:
$validationResult = $request->validate([
'name' => ['string', 'required', 'max:255'],
'email' => ['string', 'required', 'email', 'max:255', 'unique:users'],
]);
Better option than this is to create a specific request type by running
php artisan make:request YourRequest`
Your new class will be ready at app/Http/Requests where you can specify not only your rules() as you have in your array, as the messages() you wish each to output.
Then all you need to do is to replace your save(Request $request)
for save(YourRequest $request) which will kick in the validation before it triggers the method, which means you are at ease within the controller method to do the logic instead of having to double check your variables
Examples for common rules() within your class, will be:
public function rules()
{
return [
'email' => 'required|email|unique:users|max:255',
'name' => 'required|min:2|max:200',
];
}
public function messages()
{
return [
'email.required' => 'The email field is required',
'email.email' => 'The email field needs to be an email type. Ex:. type#gmail.com',
....
];
}
Obviously adjust the rules and messages to your project and liking :)