How to deal with similar validation rules in Laravel? - php

I have not so much practical experience with Laravel yet and I wondered what is the best way to deal with similar validation logic and where to put it.
Let's say I have an API resource Controller for Products with a store and an update method like so:
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:100',
'description' => 'nullable|string|max:1000',
'price' =>'required|decimal:0,2|lt:1000'
]);
return Product::create($request->all());
}
public function update(Request $request, Product $product)
{
$request->validate([
'name' => 'string|max:100',
'description' => 'nullable|string|max:1000',
'price' =>'decimal:0,2|lt:1000'
]);
return Product::update($request->all());
}
The only difference between the validation in store and update is that store adds the 'required' rule for 'name' and 'price'. My question is, if I can encapsulate both validations in one Form Request, or how can I avoid code duplication without adding unnecessary code?
With my understanding of Form Requests I would probably create two Form Request classes, StoreProductRequest and UpdateProductRequest, and maybe another helper class that defines the core validation rules. Then each Form request could call for example ProductHelper::getBaseValidationRules() and merge that with their extra requirements. Somehow I find that a bit overkill.

you can create a request for your validations and use them in your controllers
for example
php artisan make:request YOUR_REQUEST_NAME
then inside your request you can add your validations like this
public function rules()
{
return [
'name' => 'required|string|max:100',
'description' => 'nullable|string|max:1000',
'price' => 'required|decimal:0,2|lt:1000'
];
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
and in your method you can call it like this
public function update(YOUR_REQUEST_NAME $request, Product $product)
{
return Product::update($request->all());
}
for more information you can read this
https://laravel.com/docs/5.0/validation#form-request-validation
in case you want condition in the rules please check this video
https://www.youtube.com/watch?v=epMaClBOlw0&ab_channel=CodeWithDary

Okay based on the suggestions, I came up with the following solution:
I created a Form Request named ProductRequest and implemented the rules method as follows:
public function rules()
{
$rules = [
'name' => ['string', 'max:100'],
'description' => ['nullable', 'string', 'max:1000'],
'price' => ['decimal:0,2', 'lt:1000'],
];
// If the user wants to create a new Instance some fields are mandatory.
if ($this->method() === 'POST') {
$rules['name'][] = 'required';
$rules['price'][] = 'required';
}
return $rules;
}
This is fine for me. Although in a bigger project I probably would create two Form Requests, StoreProductRequest and UpdateProductRequest. They would share and update a base set of rules as I described in the question.

Related

How to validate data, protect routes, authorize routes/actions in Laravel 8

This will be a bit long question, I'm finishing my application, but there are few left things to be done before ending.
At first: I did few POST forms/functions. They works well, but in case I would not pass one of the $request data, it comes with SQL bug, I read that to manage that I need validation, so I made Request model with validation rules, but I dont know how to implement it into my Controller class.
This is how looks like my request model and create class inside of controller:
public function create(Request $request)
{
$id = Auth::id();
$event = new Event;
$event->name = $request->name;
$event->description = $request->description;
$event->address = $request->address;
$event->date_of_event = $request->date_of_event;
$event->displayed = 0;
$event->photo_patch = $request->photo_patch->store('images','public');
$event->club_id = $request->club_id;
$event->user_id = $id;
$event->save();
return redirect('events');
}
------------Request---------------------------
return [
'name' => 'required|max:50',
'description' => 'required|max:100',
'address' => 'required|max:63',
'date_of_event' =>'required',
'photo_patch' =>'required',
'club_id' =>'required',
];
Second thing to protect is to split views for admin and for user, Since I did authorization via Gate(admin-level) with column in db admin(boolean)
I'm thinking about doing validation like this:
public function index()
{
$id = Auth::id();
if (Gate::authorize('admin-level')){
$events = Event::get();
}
else{
$events = Event::where('user_id',$id)->get();
}
return view('backend/event/index', ['events' => $events]);
}
but then, comes error:
Non static method 'authorize' should not be called statically.
Is there any way to bypass that? Or, is there any better/easier way to authorize?
My third problem is to protect users from making changes by other users.
What do I mean by that.
Every user got acces only to his own club/events BUT if someone would put url for example other ID, he can edit every single club/event he want. How can I prevent it?
And my final question
I'm protecting my routes with middleware auth is there any better way to do it?
Route::middleware(['auth'])->group(function() {
Thank you in advance for anwsers.
Your gate should be called on the facade, which i do not believe you are doing. Please use the following gate class.
use Illuminate\Support\Facades\Gate;
Validation can be implemented by calling validate() on your request object. This will also automatically throw exceptions if failed.
$validated = $request->validate([
'name' => 'required|max:50',
'description' => 'required|max:100',
'address' => 'required|max:63',
'date_of_event' =>'required',
'photo_patch' =>'required',
'club_id' =>'required',
]);
Disallowing users from editing clubs. In general you control the access to objects, either with policies or with queries. This is an example with a policy.
public function find(Club $club) {
$this->authorize('view', $club);
}
class ClubPolicy {
public function view(User $user, Club $club)
{
return $club->user_id === $user->id;
}
}

How to avoid duplication in Laravel validation rules

For validating form validation rules I currently stored them in User Model and use it in Register Controller, User controller in admin panel, User Controller in APIs and some other places, but currently it's very hard to maintain because each controller needs a slightly different set of rules and when I change the rules in User Model other controllers will not work anymore. So how to avoid duplication in rules and still keep the code maintainable?
Approach I often use is to write a HasRules trait for my models, it looks something like this:
trait HasRules
{
public static function getValidationRules(): array
{
if (! property_exists(static::class, 'rules')) {
return [];
}
if (func_num_args() === 0) {
return static::$rules;
}
if (func_num_args() === 1 && is_string(func_get_arg(0))) {
return array_get(static::$rules, func_get_arg(0), []);
}
$attributes = func_num_args() === 1 && is_array(func_get_arg(0))
? func_get_arg(0)
: func_get_args();
return array_only(static::$rules, $attributes);
}
}
Looks messy, but what it does is allows you to retrieve your rules (from a static field if such exists) in a variety of ways. So in your model you can:
class User extends Model
{
use HasRules;
public static $rules = [
'name' => ['required'],
'age' => ['min:16']
];
...
}
Then in your validation (for example, in your FormRequest's rules() method or in your controllers when preparing rules array) you can call this getValidationRules() in variety of ways:
$allRules = User::getValidationRules(); // if called with no parameters all rules will be returned.
$onlySomeRules = [
'controller_specific_field' => ['required'],
'name' => User::getValidationRules('name'); // if called with one string parameter only rules for that attribute will be returned.
];
$multipleSomeRules = User::getValidationRules('name', 'age'); // will return array of rules for specified attributes.
// You can also call it with array as first parameter:
$multipleSomeRules2 = User::getValidationRules(['name', 'age']);
Don't be afraid to write some code for generating your custom controller specific rules. Use array_merge and other helpers, implement your own (for example, a helper that adds 'required' value to array if it's not there or removes it etc). I strongly encourage you to use FormRequest classes to encapsulate that logic though.
You can try using laravel's validation laravel documentation
it is really easy to use and maintain just follow these steps:
run artisan command: php artisan make:request StoreYourModelName
which will create a file in App/Http/Requests
in the authorize function set it to:
public function authorize()
{
return true;
}
then write your validation logic in the rules function:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
Custom error messages add this below your rules function:
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
Lastly to use this in your controller just add it as a parameter in your function.
public function create(Request $request, StoreYourModelName $storeYourModelName)
{
//
}
and that's all you need to do this will validate on form submission if validation passes it will go to your controller, keep in mind your validation logic does not have to be like mine thought i would show you one way that it can be done..

Laravel Post Request

I have a table called Customer and with a Get Request I can already get all the Data (which I created with phpMyAdmin) on a HTML Template.
Now I want to create a new Customer with a Post Request.
This is the way I thought it would work:
In the Controller:
public function addNewCustomer(Request $request)
{
return \app\model\Customer::create($request->all());
}
The route:
Route::post('posttest', 'CustomerController#addNewCustomer');
How can I create a validation for it?
You can add validation of form like below in addNewCustomer,
public function addNewCustomer(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
return \app\model\Customer::create($request->all());
}
For more information about input validation in Laravel Please readout Laravel Validation Documentation

Handle form submission Laravel 5

I just start learning Laravel 5, and I want to know what the proper way to handle submitted forms. I found many tutorials where we create two separate actions, where first render form, and the second actually handle form. I am came from Symfony2, where we create a single action for both, render and handle submitted form, so I want to know I need to create two separate actions because thats Laravel-way, or I can place all logic into single action, I do this like the folowing, but I dont like code what I get:
public function create(Request $request)
{
if (Input::get('title') !== null) {
$v = Validator::make($request->all(), [
'title' => 'required|unique:posts',
'content' => 'required',
]);
if ($v->fails()) {
return redirect()->back()->withErrors($v->errors());
}
$post = new Post(Input::all());
if ($post->save()) {
return redirect('posts');
}
}
return view('add_post');
}
So can somebody give me advice how I need do this properly? Thanks!
One of the most important reason to create two actions is to avoid duplicate form submissions . You can read more about Post/Redirect/Get pattern.
Another important reason is the way you keep the code cleaner. Take a look at this first change:
public function showForm(){
return view('add_post');
}
public function create(Request $request)
{
$v = Validator::make($request->all(), [
'title' => 'required|unique:posts',
'content' => 'required',
]);
if ($v->fails()) {
return redirect()->back()->withErrors($v->errors());
}
$post = new Post(Input::all());
if ($post->save()) {
return redirect('posts');
}
return redirect()->route('show_form')->withMessage();
}
The first thing that you can notice is that create() function is not rendering any view, it is used to manage the creation logic (as the name itself suggests). That is OK if you plan to stay in low-profile, but what happens when you do need to add some others validations or even better, re-utilize the code in other controllers. For example, your form is a help tool to publish a comment and you want to allow only "authors-ranked" users to comment. This consideration can be manage more easily separating the code in specific actions instead making an if-if-if-if spaghetti. Again...
public function showForm(){
return view('add_post');
}
public function create(PublishPostRequest $request)
{
$post = new Post($request->all());
$post->save()
return redirect('posts');
}
Take a look on how PublishPostRequest request takes place in the appropriated function. Finally, in order to get the best of Laravel 5 you could create a request class to keep all the code related with validation and authorization inside it:
class PublishPostRequest extends Request{
public function rules(){
return [
'title' => 'required|unique:posts',
'content' => 'required',
]
}
public function authorize(){
$allowedToPost = \Auth::user()->isAuthor();
// if the user is not an author he can't post
return $allowedToPost;
}
}
One nice thing about custom request class class is that once is injected in the controller via function parameter, it runs automatically, so you do not need to worry about $v->fails()

Laravel 5 custom validation rule for existing tags

I'm searching for a cleaner way to validate tags when storing a Post.
All of the input validation takes place within my custom request StorePostRequest. The problem is that I need to check whether the given tags exist in the database, only existing tags are allowed. The function $request->input('tags') returns a string with comma seperated values, for example: Tag1,Tag2,Tag3.
Here is the code:
/**
* Store a newly created resource in storage.
*
* #param StorePostRequest $request
* #return Response
*/
public function store(StorePostRequest $request)
{
//THIS PIECE OF CODE
$tags = explode(',', $request->input('tags'));
$tags = Tag::whereIn('title', $tags)->lists('id');
if(count($tags) < 1)
{
return redirect()->back()->withInput()->withErrors([ trans('tags.min') ]);
}
else if(count($tags) > 5)
{
return redirect()->back()->withInput()->withErrors([ trans('tags.max') ]);
}
//TILL HERE
$post = $request->user()->posts()->create([
'slug' => unique_slug('Post', $request->input('title')),
'title' => $request->input('title'),
'description' => $request->input('description'),
'summary' => $request->input('summary'),
]);
$post->tags()->attach($tags);
return redirect(route('theme.post.show', [$theme->slug, $post->slug]))->with(['success', trans('messages.post.store')]);
}
The code is a little sloppy and redundant when using it in multiple controllers.
In order to solve this, I've created a ValidationServiceProvider to extend the core validator rules. Something like this:
$this->app['validator']->extend('tags', function ($attribute, $value, $parameters)
{
$tags = explode(',', $value);
$tags = Tag::whereIn('title', $tags)->lists('id');
if(count($tags) < 1 || count($tags) > 5))
{
return false;
}
});
Pretty neat. The thing is I still need to be able to access the $tags variable within the controller (because of ->attach($tags)).
Is there a better way of tackling this problem? Or should I stop thinking and just use (and repeat) the code I have?
Thanks in advance, hope it makes some sence.
I am assuming that you understand the use of this class because I have seen that you have defined StorePostRequest class. So, just for clarify, the rules method could looks like:
public function rules()
{
return [
'tags' => ['required', 'tags'] //kb
];
}
Finally, with all the tools in correct place, you only make manipulate the data in your controllers like this:
public function store(StorePostRequest $request)
{
// at this point, the tags are already validated, so we, proceed get them:
$tags = explode(',', $$request->get('tags'));
$post = $request->user()->posts()->create([
'slug' => unique_slug('Post', $request->input('title')),
'title' => $request->input('title'),
'description' => $request->input('description'),
'summary' => $request->input('summary'),
]);
$post->tags()->attach($tags);
return redirect(route('theme.post.show', [$theme->slug, $post->slug]))->with(['success', trans('messages.post.store')]);
}
Keep in mind that inyecting StorePostRequeststore in the controller's function it is already validating and running the rules.
That is enough if you really has defined the StorePostRequest's rules correctly.
foreach($request->tags as $k=>$tags){
$this->validate($request, [
'tags.'.$k => 'required|string|max:20'
]);
}

Categories