Laravel Index Method: A Guide to Controller Essentials
The controller architecture and the routes are setup and ready for us to start tackling. The first one on the list is index. We’re going to pull all of the data out of the table, put it into a view, and return it back to the user.
The Route and the Controller Method
We currently have the following code already created.
Route::prefix('/personalcars')->group(function() {
Route::get('/', [PersonalCarController::class, 'index']);
// ...
});
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PersonalCarController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
// get data from model
// send data to view
// return view to user
}
// ...
}
We don’t have to make the modification on our route file. We simply need to work on our controller and create a new view.
The Process
First on our list is retrieving all of the data from our personal_cars
table. At this point, I envision a table in our view with the retrieved contents. On the main page, I don’t want to list out all of the images, I simply want information.
public function index()
{
$cars = PersonalCar::with(['brand', 'model'])->get();
dd($cars);
// send data to view
// return view to user
}
We grabbed all of our data and we’re dumping it to the user. When the user visits the route /personalcars/
, they’ll see the following information.
We can expand through each of the returned items to see the details.
There’s quite a bunch of information and it’s exactly what we needed.
Time to create our view. We’ll be utilizing components. It all comes back around. I know that we haven’t looked at components in a while, but we’ll use the following template to get us started. If you’re following along with the code, it’s located here: /views/components/layouts/app.blade.php
.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title }}</title>
<!-- Fonts -->
<link href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Styles -->
<style>
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.text-center{text-align:center}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark\:text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.dark\:text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}}
</style>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
</head>
<body>
<x-layouts.header.main page-title="{{ $title }}" />
{{ $slot }}
<x-layouts.footer.main page-title="{{ $title }}" />
</body>
</html>
The file that we’ll create that utilizes this template is: /resources/views/personalcars/index.blade.php
.
<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12" style="height:600px;">
<div class="items-center text-center lg:text-left px-8 md:px-12 lg:w-full">
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Year
</th>
<th scope="col" class="px-6 py-3">
Make
</th>
<th scope="col" class="px-6 py-3">
Model
</th>
<th scope="col" class="px-6 py-3">
Exterior Color
</th>
<th scope="col" class="px-6 py-3">
Current Value
</th>
</tr>
</thead>
<tbody>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
2003
</th>
<td class="px-6 py-4">
Chevrolet
</td>
<td class="px-6 py-4">
Corvette
</td>
<td class="px-6 py-4">
Silver
</td>
<td class="px-6 py-4">
$25,000
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</x-layouts.app>
The file that we’ll create that utilizes this template is: /resources/views/personalcars/index.blade.php
.
<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12" style="height:600px;">
<div class="items-center text-center lg:text-left px-8 md:px-12 lg:w-full">
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Year
</th>
<th scope="col" class="px-6 py-3">
Make
</th>
<th scope="col" class="px-6 py-3">
Model
</th>
<th scope="col" class="px-6 py-3">
Exterior Color
</th>
<th scope="col" class="px-6 py-3">
Current Value
</th>
</tr>
</thead>
<tbody>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
2003
</th>
<td class="px-6 py-4">
Chevrolet
</td>
<td class="px-6 py-4">
Corvette
</td>
<td class="px-6 py-4">
Silver
</td>
<td class="px-6 py-4">
$25,000
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</x-layouts.app>
It’s just a simple table with some additional styling through Tailwind.
The view will be called from our index()
method in our PersonalCarController
.
public function index()
{
$cars = PersonalCar::with(['brand', 'model'])->get();
return view('personalcars/index', [
'title' => 'Personal Cars',
]);
}
No data is currently passed. When we refresh the page, we see the following page with our table.
We simply need to pass the data to our view now and display it.
public function index()
{
$cars = PersonalCar::with(['brand', 'model'])->get();
return view('personalcars/index', [
'title' => 'Personal Cars',
'cars' => $cars,
]);
}
<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12" style="height:600px;">
<div class="items-center text-center lg:text-left px-8 md:px-12 lg:w-full">
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Year
</th>
<th scope="col" class="px-6 py-3">
Make
</th>
<th scope="col" class="px-6 py-3">
Model
</th>
<th scope="col" class="px-6 py-3">
Exterior Color
</th>
<th scope="col" class="px-6 py-3">
Current Value
</th>
</tr>
</thead>
<tbody>
@foreach($cars as $car)
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ $car->year }}
</th>
<td class="px-6 py-4">
{{ $car->brand->name }}
</td>
<td class="px-6 py-4">
{{ $car->model->name }}
</td>
<td class="px-6 py-4">
{{ $car->exterior_color }}
</td>
<td class="px-6 py-4">
{{ $car->current_value }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</x-layouts.app>
When we refresh our page, we get the following results.
Looks pretty good. It pulls up all of our information from the database. We may want to tweak a couple of things:
- sort the results by year, descending
- Convert the current value from pennies to dollars
The first one is simple. To sort the values, we simply add an orderBy
method to our model call.
public function index()
{
$cars = PersonalCar::with(['brand', 'model'])->orderBy('year', 'desc')->get();
return view('personalcars/index', [
'title' => 'Personal Cars',
'cars' => $cars,
]);
}
The next one can be handled a couple of different ways. We could make this modification in the view itself.
<td class="px-6 py-4">
${{ number_format($car->current_value / 100, 2, '.', ',') }}
</td>
If we refresh our page, we see the following results, which is exactly what we expected. As an aside, I really do wish the value of my cars was this much!
Although we can do this, we won’t. We’ll do it through the model. First, I’ll show you how it used to be done and then I’ll show you how it’s done now. Inside of our PersonalCar
model, we need to create the following method.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PersonalCar extends Model
{
// ...
public function getCurrentValueAttribute($value)
{
return "$" . number_format($value / 100, 2, '.', ',');
}
}
The getCurrenValueAttribute()
is named specifically. It starts off with the word get
followed by the property name converted to camel-case (i.e. current_value
becomes currentValue
) and ends with the word Attribute
. We pass a $value
argument to it and then work on it. The $value
argument will automatically be passed when we call our $car->current_value
in the view. It then formats the number and returns it.
The new way to do this is by utilizing the Attribute
instance. We strip out the get
and Attribute
words from our function and instead specify that this function will return a new Attribute
. The Attribute
constructor receives a get
argument and the function definition is outlined.
<?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, '.', ','),
);
}
}
Again, we’ll cover this extensively at a later point, but here’s a quick introduction for you. We can now eliminate the code from our view and return it to the regular attribute call.
<td class="px-6 py-4">
{{ $car->current_value }}
</td>
When we refresh the page, we can see that everything still works.
The full code now looks like this for our controller, view and model.
<?php
namespace App\Http\Controllers;
use App\Models\PersonalCar;
use Illuminate\Http\Request;
class PersonalCarController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$cars = PersonalCar::with(['brand', 'model'])->orderBy('year', 'desc')->get();
return view('personalcars/index', [
'title' => 'Personal Cars',
'cars' => $cars,
]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
// Return the form to the user
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// get data from form
// validate data
// insert data into table
// return message that insertion was successful
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
// Look up resource with id
// Send data to show view
// Return view to user through route
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
// get resource with the help of the PersonalCar model
// send data to edit view
// the view injects the data into the form
// the view is returned to the user through the route
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
// get data from form
// update the resource with the form data
// return a success message
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
// delete the record from the table
// return redirect with success message
}
}
<?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
{
use HasFactory;
protected $fillable = [
'year', 'is_manual', 'exterior_color', 'purchase_amount', 'current_value', 'sales_amount', 'date_purchased', 'date_sold'
];
public function brand()
{
return $this->belongsTo(PersonalCarBrand::class, 'personal_car_brand_id', 'id');
}
public function model()
{
return $this->belongsTo(PersonalCarModel::class, 'personal_car_model_id', 'id');
}
public function images()
{
return $this->belongsToMany(Image::class);
}
protected function currentValue(): Attribute
{
return Attribute::make(
get: fn($value) => "$" . number_format($value / 100, 2, '.', ','),
);
}
}
<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12" style="height:600px;">
<div class="items-center text-center lg:text-left px-8 md:px-12 lg:w-full">
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Year
</th>
<th scope="col" class="px-6 py-3">
Make
</th>
<th scope="col" class="px-6 py-3">
Model
</th>
<th scope="col" class="px-6 py-3">
Exterior Color
</th>
<th scope="col" class="px-6 py-3">
Current Value
</th>
</tr>
</thead>
<tbody>
@foreach($cars as $car)
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ $car->year }}
</th>
<td class="px-6 py-4">
{{ $car->brand->name }}
</td>
<td class="px-6 py-4">
{{ $car->model->name }}
</td>
<td class="px-6 py-4">
{{ $car->exterior_color }}
</td>
<td class="px-6 py-4">
{{ $car->current_value }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</x-layouts.app>
See you next time when we tackle the show
method.
Laravel Series
Continue your Laravel Learning.
Streamlining Laravel: Routes and Controllers High-Level Overview.
Laravel – P39: CMP – Routes And Controllers High- Level Setup
Discover how to efficiently set up routes and controllers in Laravel. A high-level guide to streamline your application’s foundational structure.
Laravel Index Method: A Guide to Controller Essentials
Laravel – P40: CMP – Controller Index
Explore the role of the Index Method in Laravel controllers. Learn how to effectively utilize this method to manage and display data in your application.
Understanding the Show Method for Data Display
Laravel – P41: CMP – Controller Show
Learn how to use Laravel’s Show Method in controllers to effectively display individual data records. A comprehensive guide for streamlining your app.