Handle form submission Laravel 5 - php

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()

Related

How to deal with similar validation rules in Laravel?

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.

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 - limit FormRequest to certain parameters

In a form request class I use a method like this to validate input data.
class SignupRequest extends FormRequest
{
...
public function rules()
{
return [
'user.email' => 'required_with:user|email',
'user.domain_name' => 'required_with:user|string',
'user.password' => 'required_with:user|string|min:8',
'user.username' => 'required_with:user',
];
}
...
}
Later in a controller I use something like this
$data = $request->get('user', []);
return $this->response($this->userService->create($data, false), 201);
I want somehow to write to my SignupRequest which fields it should allow to be passed. So when later I get $data = $request->get('user', []); I'm sure there are only allowed fields in it.
Is this possible inside the FormRequest?
P.S. I'm aware of $request->only(['field1', 'field2', 'field3']) way, but if I want to limit the fields in SignupRequest extends FormRequest. Because if I use $request->only([...]) in my code several times, I would have to change it several times later. I want to keep it in one place.
You wouldn't need to do this with the request.
One option would be to do something like:
$user = $request->input('user', []);
$data = array_only($user, ['email', 'domain_name', 'password', 'username']);
Or you could even inline it:
$data = array_only($request->input('user', []), ['email', 'domain_name', 'password', 'username']);
Hope this helps!
FormRequest is meant to validate your request data, not control them. You could always extract the inputs you need by doing so.
$data = $request->only(['user.name', 'user.password']);
Edit : Based on your comment, you can do something like this. This allows you to store all the field names within a single request to keep them organised and easier to update.
Add this to your SignupRequest
public function loginData()
{
return array_only($this->input('user', []), ['username', 'password']);
}
Use it in the controller like so
$request->loginData();
return $this->response($this->userService->create($request->loginData(), false), 201);

How to filter records with RESTful models and controllers in Yii2

I'm creating a RESTful API with Yii2 and have successfully setup a model named Contacts by following the Quick Start Tutorial*. I love how records can be created, listed, updated and deleted without creating any actions.
However I can't see how to filter results. I would like to only return contacts where contact.user_id is equal to 1 (for example) as it currently will reply with all records. Is this possible without creating the actions?
I am unsure also how I can limit results. From what I've read I feel it should append the URI with ?limit=5.
http://www.yiiframework.com/doc-2.0/guide-rest-quick-start.html
You should return a dataprovider instead of a set of objects, that supports pagination for you.
Perhaps this approach will be a bit more useful:
public function actionIndex()
{
return new \yii\data\ActiveDataProvider([
'query' => Contact::find()->where(['user_id' => \Yii::$app->user-id]),
]);
}
You could also leave the index action intact, but provide the preset action with a prepareDataProvider-callback:
public function actions()
{
$actions = parent::actions();
$actions['index']['prepareDataProvider'] = function($action)
{
return new \yii\data\ActiveDataProvider([
'query' => Contact::find()->where(['user_id' => \Yii::$app->user-id]),
]);
};
return $actions;
}
Hope that helps.
I have had to override the index method despite not wanting to. My solution looks like this:
public function actions()
{
$actions = parent::actions();
unset($actions['index']);
return $actions;
}
public function actionIndex()
{
return Contact::findAll(['user_id' => \Yii::$app()->user-id]);
}
I guess this solution means I need to write my own pagination code however which is something else I was hoping to avoid.

I can not bind redirect::to() and routes together, it does not redirect

Everything looks right to me. It's so simple, but I dont know. I've looked everywhere.
Problem: It doesn't redirect. It doesn't give error nothing happens.
But when I enter the browser http://site.dev/fail
it shows "fail" word on screen (so it works).
routes.php:
Route::post('getir' , 'Ahir\Ticket\Controllers\TicketController#postInsert');
Route::get('fail', function() { return 'fail'; });
Route::get('success', function() { return 'success'; });
edit everything
Scenario:
on site.dev/ (homepage) I press submit that form has this.
form action="getir" method="POST" role="form"
so button redirect me to
Route::post('getir' , 'Ahir\Ticket\Controllers\TicketController#postInsert');
so this postInsert is triggered below at controller ticket.
controller ticket:
<?php namespace Ahir\Ticket\Controllers;
use BaseController, Input;
//use Ahir\Ticket\Repositories\TicketInterface;
use Ahir\Ticket\Adapters\AdapterInterface ;
class TicketController extends BaseController {
public function __construct(AdapterInterface $adapter) //TicketInterface $repository
{
//$this->repository = $repository;
$this->adapter = $adapter;
}
public function postInsert()
{
$this->adapter->postInsert();
}
}
then it comes here
codes
public function postInsert()
{
// create the validation rules ------------------------
$rules = array(
'title' => 'required',
'content' => 'required',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
// i added here return vardump('fail'); it displays on screen.
// so i know that program comes here
// but the redirect below neither gives error nor redirect.
//nothing happens here. idk why!
return Redirect::to('fail')->withErrors($validator);
} else {
// validation successful ---------------------------
$this->obj->insert([
'title' => Input::get('title') ,
'content' => Input::get('content')
]);
//here DOESNT work too.
return Redirect::to('success');
}
The problem is that you don't return anything from the calling function.
Your application calls the postInsert() method on your ticket controller. This function calls another function which returns a Redirect.
But you don't pass that returned Redirect back to the application, so the postInsert() function just terminates without any output. The application doesn't know what happens within the postInsert() function, it just waits for something to be returned. And since nothing is returned, the HTTP response is simply empty. In order to pass that Redirect back to the application, you also have to return it from the calling function:
public function postInsert()
{
return $this->adapter->postInsert();
}

Categories