Mastering Form Validation in Laravel Controllers
Adding data through the form is relatively straight forward. Making sure that the data is accurate is part of the process. That’s what this article is about. There are a couple of additional tweaks that need to occur such as inserting the penny amount, not the dollar amount, into the table.
https://medium.com/geekculture/laravel-p42-controller-create-store-cmp-eabfcc50b234
Setters
Just like we modified the attribute when we were retrieving it, we can modify the attribute when we’re setting it. The best place to do this, if it’s going to be consistent, will be in the model: PersonalCar
.
The three fields that we need to modify during insertion are:
current_value
purchase_amount
sales_amount
Those attribute getters already exist in the PersonalCar
model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PersonalCar extends Model
{
// ...
protected function currentValue(): Attribute
{
return Attribute::make(
get: fn($value) => "$" . number_format($value / 100, 2, '.', ','),
);
}
protected function purchaseAmount(): Attribute
{
return Attribute::make(
get: fn($value) => "$" . number_format($value / 100, 2, '.', ','),
);
}
protected function salesAmount(): Attribute
{
return Attribute::make(
get: fn($value) => ($value == 0 ? "N/A" : "$" . number_format($value / 100, 2, '.', ',')),
);
}
// ...
}
To modify the value during insertion, we simply need to add a set
to our Attribute::make
method. If we divided the pennies by 100 to get our dollar value, then to convert to pennies we need to multiply the dollar value by 100.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PersonalCar extends Model
{
// ...
protected function currentValue(): Attribute
{
return Attribute::make(
get: fn($value) => "$" . number_format($value / 100, 2, '.', ','),
set: fn($value) => $value * 100,
);
}
protected function purchaseAmount(): Attribute
{
return Attribute::make(
get: fn($value) => "$" . number_format($value / 100, 2, '.', ','),
set: fn($value) => $value * 100,
);
}
protected function salesAmount(): Attribute
{
return Attribute::make(
get: fn($value) => ($value == 0 ? "N/A" : "$" . number_format($value / 100, 2, '.', ',')),
set: fn($value) => $value * 100,
);
}
// ...
}
That’s it. Now, when we insert a value, we can see that it’s stored in penny format.
Validation
The next piece is validation. How do you validate data in Laravel? This will be extensively covered at a later point, but for now we’ll take a look at the simplest way to validate.
Our injected $request
argument has a method called validate
. It accepts an array of key value pairs where the key is the form field and the value are the validation requirements. For example, if I wanted to make our year
required, be an integer, and have a maximum number of characters equal to 4, we could do something like this:
$request->validate([
'year' => 'required|integer|max:4',
]);
I’m going to repeat this process for all of our fields.
public function store(Request $request)
{
$request->validate([
'year' => 'required|integer|max:4',
'make' => 'required|max:255',
'model' => 'required|max:255',
'is_manual' => 'required|boolean',
'exterior_color' => 'required|max:255',
'purchase_amount' => 'numeric',
'current_value' => 'numeric',
'sales_amount' => 'numeric|nullable',
'date_purchased' => 'required|date',
'date_sold' => 'date|nullable',
]);
$brand = PersonalCarBrand::firstOrCreate([
'name' => $request->make,
], [
'slug' => str_replace(" ", "-", strtolower($request->make))
]);
$model = PersonalCarModel::firstOrCreate([
'name' => $request->model,
], [
'slug' => str_replace(" ", "-", strtolower($request->model))
]);
$car = new PersonalCar;
$car->year = $request->year;
$car->brand()->associate($brand);
$car->model()->associate($model);
$car->is_manual = $request->is_manual;
$car->exterior_color = $request->exterior_color;
$car->purchase_amount = $request->purchase_amount;
$car->current_value = $request->current_value;
$car->sales_amount = $request->sales_amount == 0 ? 0 : $request->sales_amount;
$car->date_purchased = $request->date_purchased;
$car->date_sold = $request->date_sold;
$car->save();
return redirect()->to('/personalcars/');
// return message that insertion was successful
}
Straightforward enough. If you want to see a full list of applicable validation requirements, you can check them out here:
https://laravel.com/docs/9.x/validation#available-validation-rules
But how does Laravel return those errors if validation breaks? It does it automatically. We simply need to specify how those errors are displayed. The errors are going to be returned in what’s called the $errors
message bag to the page that submitted the form (in our instance, crate.blade.php
). We can add the following piece of code anywhere within the create
view, and it will display if validation errors occur.
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
I’ll place it right above all of the form fields.
<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12">
<div class="flex justify-center items-center w-full">
<div class="w-1/2 bg-white rounded shadow-2xl p-8 m-4">
<h1 class="block w-full text-center text-gray-800 text-2xl font-bold mb-6">Add New Car</h1>
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<form action="/personalcars/" method="post">
@csrf
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="year">Year</label>
<input class="border py-2 px-3 text-grey-800" type="number" name="year" id="year" min="1920" max="{{ date('Y') + 1 }}" placeholder="2003">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="make">Make</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="make" id="make" placeholder="Chevy">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="model">Model</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="model" id="model" placeholder="Corvette">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="password">Transmission</label>
<div class="flex items-center mb-4">
<input id="is-manual-1" type="radio" name="is_manual" value="1" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300" checked>
<label for="is-manual-1" class="text-sm font-medium text-gray-900 ml-2">
Manual
</label>
</div>
<div class="flex items-center mb-4">
<input id="is-manual-2" type="radio" name="is_manual" value="0" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300">
<label for="is-manual-2" class="text-sm font-medium text-gray-900 ml-2">
Automatic
</label>
</div>
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="exterior_color">Exterior Color</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="exterior_color" id="exterior_color" placeholder="Blue">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="purchase_amount">Purchase Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="purchase_amount" id="purchase_amount" placeholder="9532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="current_value">Current Value (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="current_value" id="current_value" placeholder="95532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="sales_amount">Sales Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="sales_amount" id="sales_amount" placeholder="0.00">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_purchased">Date Purchased</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_purchased" id="date_purchased">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_sold">Date Sold</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_sold" id="date_sold">
</div>
<div class="flex flex-col mb-4">
<div class="mb-8">
<label class="mb-2 font-bold text-lg text-gray-900 block" for="file">Select Image</label>
<input type="file" name="file" id="file" class="mb-2 text-lg text-gray-900" />
</div>
</div>
<button class="block bg-green-400 hover:bg-green-600 text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Save Car</button>
</form>
</div>
</div>
</div>
</x-layouts.app>
Now, if I miss anything, I’ll receive the following messages.
We’ll tackle repopulating form fields and customizing message location so that they appear next to the appropriate field in a later article. For now, having a message dump is good enough.
We also want to display a success message if everything is good. Our user is redirected to the index.blade.php
view when the vehicle is successfully inserted. It would be nice to let them know that their vehicle was inserted. I’ll add a green banner to our index page. First, we need to send our message.
To send a message, we simply append a with()
method to our redirect()
and specify the message name and the message content.
return redirect()->to('/personalcars/')->with('status', 'Your car has been added.');
Our store
method is now complete.
<?php
namespace App\Http\Controllers;
use App\Models\PersonalCar;
use App\Models\PersonalCarBrand;
use App\Models\PersonalCarModel;
use Faker\Provider\Person;
use Illuminate\Http\Request;
class PersonalCarController extends Controller
{
// ...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request)
{
$validated = $request->validate([
'year' => 'required|integer|max:4',
'make' => 'required|max:255',
'model' => 'required|max:255',
'is_manual' => 'required|boolean',
'exterior_color' => 'required|max:255',
'purchase_amount' => 'numeric',
'current_value' => 'numeric',
'sales_amount' => 'numeric|nullable',
'date_purchased' => 'required|date',
'date_sold' => 'date|nullable',
]);
$brand = PersonalCarBrand::firstOrCreate([
'name' => $request->make,
], [
'slug' => str_replace(" ", "-", strtolower($request->make))
]);
$model = PersonalCarModel::firstOrCreate([
'name' => $request->model,
], [
'slug' => str_replace(" ", "-", strtolower($request->model))
]);
$car = new PersonalCar;
$car->year = $request->year;
$car->brand()->associate($brand);
$car->model()->associate($model);
$car->is_manual = $request->is_manual;
$car->exterior_color = $request->exterior_color;
$car->purchase_amount = $request->purchase_amount;
$car->current_value = $request->current_value;
$car->sales_amount = $request->sales_amount == 0 ? 0 : $request->sales_amount;
$car->date_purchased = $request->date_purchased;
$car->date_sold = $request->date_sold;
$car->save();
return redirect()->to('/personalcars/')->with('status', 'Your car has been added.');
}
// ...
}
To display our status
message in our index.blade.php
file, we simply need to check if the status
session variable exists. If it does, output the message.
@if (session('status'))
<div class="block bg-green-200 p-4">
{{ session('status') }}
</div>
@endif
If we refresh the index page, the message will disappear. In only stays there until the page is refreshed.
We’ve tackled all of the issues and can now move to adding images. Images will be looked at in the next article.
Laravel Series
Continue your Laravel Learning.
Controller Essentials: Understanding Laravel’s Create/Store Methods
Laravel – P42: CMP – Insert Data
Explore Laravel’s Create/Store methods in controllers for seamless data creation and storage. A guide to efficiently handling form submissions in Laravel.
Mastering Form Validation in Laravel Controllers
Laravel – P43: CMP – Form Validation
Learn how to implement form validation in Laravel controllers to ensure data integrity and improve user input handling in your applications.
Implementing Single Image Upload in Forms
Laravel – P44: CMP – Form Single Image Upload
Learn how to implement single image uploads in Laravel forms. A step-by-step guide to handling and storing images effectively in your application..