In this tutorial we’ll look into through() function for manipulating data inside Laravel collection. As we’ve seen in previous article that map() and filter() are two important functions which are used to manipulate and filter data of a collection. However, these functions return Collection type and Collection does not have paginate() function (well, we need pagination in most of the cases). So, we had to work around and install a separate package spatie/laravel-collection-macros in order to incorporate paginate() function into Collection class.
What if you need to preserve pagination during collection manipulation without employing any extra package. That’s where through() comes in as handy function; being the method of LengthAwarePaginator class, through() preserves the pagination while modification of data is being done. Let’s get a grasp of this by example…
Here, we’ll setup a Laravel 9 app with relevant data, routes and views, and alongside we’ll use through() function to manipulate collection’s data while preserving pagination.
Laravel 9.x requires a minimum PHP version of 8.0. This tutorial assumes that you are using Linux, MacOS or WSL on Windows and Node.js, Composer and PHP 8 along with required modules i.e bcmatch, sqlite, mbstring, xml, zip, gd, mcrypt are properly installed. Also make sure that SQLite is installed as we’ll be using SQLite to keep things simple and quick; you may use MySQL or PostgreSQL as you like, however make sure the corresponding PHP module is installed too.
Using Composer, enter following command in terminal to install Laravel 9;
composer create-project laravel/laravel app 9.*
Now go inside newly created app directory by executing;
cd app
Then, setup database and Laravel environment file. Here, I’m configuring SQLite database for simplicity purposes.
Being in parent directory (app), execute following command in order to create new SQLite database file;
touch database/database.sqlite
Then find .env config file in the parent directory, open it and amend DB_CONNECTION line as follows;
DB_CONNECTION = sqlite
You can remove DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD from .env when using SQLite database.
Now, enter the following command to create Employee model along with the migration:
php artisan make:model Employee -m
Open newly created migration file database/migrations/(datestamp)_create_employees_table.php and then insert the desired table fields;
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('employees', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->text('address');
$table->integer('age');
$table->integer('salary');
$table->enum("department", ["Finance", "Admin", "Marketing", "Production"]);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('employees');
}
};
Enter following command in parent directory to run the above migration;
php artisan migrate
Now, create factory for generating test records by executing following command;
php artisan make:factory EmployeeFactory
Then, insert following code into database/factories/EmployeeFactory.php;
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class EmployeeFactory extends Factory
{
public function definition()
{
return [
"name" => $this->faker->name(),
"email" => $this->faker->unique()->safeEmail,
"address" => $this->faker->address,
"age" => $this->faker->numberBetween(25, 65),
"salary" => $this->faker->numberBetween(25000, 100000),
"department" => $this->faker->randomElement([
"Finance", "Admin", "Marketing", "Production"
]),
];
}
}
In order to populate database table quickly, let’s enter following command to invoke tinker;
php artisan tinker
And then enter following command to create 50 employees;
Employee::factory(50)->create()
Type ‘exit’ and press Enter to quit tinker.
Now, for view files first install Breeze starter kit by entering following command;
composer require laravel/breeze:* --dev
Then, run following command to install Breeze and compile UI assets for first time;
php artisan breeze:install
Taking short route here we’ll define functionality within routes, so open routes/web.php and change it as follows;
<?php
use Illuminate\Support\Facades\Route;
use App\Models\Employee;
Route::get('/', function () {
return view('welcome');
});
Route::get('/map', function () {
$employees = Employee::paginate(10)
->through(function ($item, $key) {
$item->salary = round(($item->salary / 12), 2);
return $item;
});
return view('empmap', compact('employees'));
});
Route::get('/change', function () {
$employees = Employee::paginate(10)
->through(fn ($item) => [
'name' => $item->name,
'email' => $item->email,
'age' => $item->age,
'monthly_salary' => round(($item->salary / 12), 2),
'department' => $item->department,
]);
return view('empchange', compact('employees'));
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
require __DIR__.'/auth.php';
Here we’ve inserted two routes; first route ‘/map‘ will render the view empmap. The second route ‘/change‘ will render the view empchange. First route’s through() function is straight forward – we just iterate each item within $employees and modify its single property i.e., salary, which we divide by 12 in order to present monthly salary and alongside round off the amount to couple of decimals.
In second route’s through() function we have redefined the data within $employees as a new array of data. Here through() function returns paginated object along with the array containing redefined data. We are free to modify the data as we want e.g., we even let go of unnecessary fields (id, address and modification dates) of the original data and replaced the field name from ‘salary‘ to ‘monthly_salary‘.
Since returned data via through() in second route is an array, therefore, it’ll be accessed just like an array i.e., $item[‘name’] not like object property i.e., $item->name. Let’s create the view files to understand this difference – enter following command to create first view file inside resources/views folder;
touch resources/views/empmap.blade.php
Then, insert following code inside resources/views/empmap.blade.php;
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div class="p-4">
<table class="shadow-lg">
<thead>
<tr class="bg-gray-400 text-white font-extrabold" >
<td class="px-4 py-1 text-center" >ID</td>
<td class="px-4 py-1">Name</td>
<td class="px-4 py-1">Email</td>
<td class="px-4 py-1 w-30">Address</td>
<td class="px-4 py-1">Age</td>
<td class="px-4 py-1">Salary</td>
<td class="px-4 py-1">Department</td>
</tr>
</thead>
<tbody>
@foreach($employees as $employee)
<tr>
<td class="border px-4 py-1 text-center">{{ $employee->id }}</td>
<td class="border px-4 py-1">{{ $employee->name }}</td>
<td class="border px-4 py-1">{{ $employee->email }}</td>
<td class="border px-4 py-1">{{ $employee->address }}</td>
<td class="border px-4 py-1">{{ $employee->age }}</td>
<td class="border px-4 py-1">{{ $employee->salary }}</td>
<td class="border px-4 py-1">{{ $employee->department }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="p-4">
{{$employees->links()}}
</div>
</div>
</body>
</html>
Next, create second view file inside resources/views folder by entering following command;
touch resources/views/empchange.blade.php
Then, insert following code inside resources/views/empchange.blade.php:
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div class="p-4">
<table class="shadow-lg">
<thead>
<tr class="bg-gray-400 text-white font-extrabold" >
<td class="px-4 py-1">Name</td>
<td class="px-4 py-1">Email</td>
<td class="px-4 py-1">Age</td>
<td class="px-4 py-1">Salary</td>
<td class="px-4 py-1">Department</td>
</tr>
</thead>
<tbody>
@foreach($employees as $employee)
<tr>
<td class="border px-4 py-1">{{ $employee['name'] }}</td>
<td class="border px-4 py-1">{{ $employee['email'] }}</td>
<td class="border px-4 py-1">{{ $employee['age'] }}</td>
<td class="border px-4 py-1">{{ $employee['monthly_salary'] }}</td>
<td class="border px-4 py-1">{{ $employee['department'] }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="p-4">
{{$employees->links()}}
</div>
</div>
</body>
</html>
Finally execute following command in parent directory to compile UI assets:
npm run build
Execute following command to run PHP development server;
php artisan serv
Open browser and enter following URL in address bar and observe the modified data with pagination intact;
http://localhost:8000/map
Then, enter the second route to see the paginated data with redefined fields;
http://localhost:8000/change