This Laravel Eloquent tutorial is going to be one hell of a ride! We're diving deep into everything you need to know about Laravel Eloquent. From model creation, CRUD operations, query optimization, advanced features, and relationships. You'll have your database interactions in Laravel applications on lock after this.
Laravel’s most trusted tool comes in the form of its very own ORM system Eloquent. It’s elegant and super smooth interface makes working with databases an absolute breeze. By cutting out all the fuss when it comes to complex SQL queries - developers can write minimal code while getting their database operations done stress free. This approach offers enhanced readability, maintainability and lets them do more with less worry! That’s just the tip of the iceberg though - things get real interesting once you pull back the curtain on all those fancy advanced features like relationship mapping, soft deletes, and event handling. Pulling data from databases has never been so simple or so intuitive!
And that ain’t even half of it! With Eloquent's active record implementation each model corresponds directly with a single table in your database. So you’ll never have to go digging down for specific records again!
It doesn’t end there either... This powerful ORM system not only boosts developer productivity but also fits like a glove into Laravel's ecosystem too. When combining these two forces it creates a comprehensive solution for application data management.
1. Steps to Build and Test Relational Data Models using Laravel Eloquent
1.1 Create a New Laravel Project
But before we begin make sure Composer is installed on your system as it is essential for installing Laravel and other PHP packages. Once you’re good head over to your terminal or command prompt and run the following command to create a new Laravel project:
sudo mkdir -p /opt/projects
sudo chmod 777 /opt/projects
cd /opt/projects
composer create-project --prefer-dist laravel/laravel LaravelEloquentTutorial
This command creates a new Laravel project named LaravelEloquentTutorial
.
1.2 Configure the Environment
Alright now let's get down to business… You got this champ! First things first though… Make sure you’ve got your MySQL database created because we’re gonna need it for this tutorial. For reference — We created our MariaDB database already (and will use that same one here).
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=my_laravel_db DB_USERNAME=user1 DB_PASSWORD=Passw0rd
1.3 Creating Models and Migrations
Eloquent models are the heart of Laravel's ORM. Create models for Flight
and Passenger
along with their migrations:
php artisan make:model Flight --migration
php artisan make:model Passenger --migration
1.4 Define Model Attributes and Relationships
In the Flight
model, define attributes that can be mass-assigned using the $fillable
property and specify the relationship with Passenger
using the hasMany()
method inside app/Models/Flight.php
.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
use HasFactory;
protected $table = 'my_flights';
protected $primaryKey = 'flight_id';
public $incrementing = false;
protected $keyType = 'string';
public $timestamps = false;
protected $fillable = ['flight_id', 'airline']; // Add attributes you want to be mass-assignable
// A Flight has many Passengers
public function passengers()
{
return $this->hasMany(Passenger::class, 'flight_id');
}
}
$table
: By default, Eloquent assumes the table name is the plural form of the model name in snake_case. If your table name differs from this convention, specify it using the$table
property.$primaryKey
: Eloquent assumes that each table has a primary key column namedid
. If this is not the case, you can set the$primaryKey
property to the name of your custom key.$incrementing
: If your primary key is not an auto-incrementing integer, set this property tofalse
. This is common for non-integer or UUID primary keys.$keyType
: This specifies the data type of the primary key. By default, it's set to'int'
. If your primary key is a string (such as a UUID), you should change this to'string'
.$timestamps
: By default, Eloquent expectscreated_at
andupdated_at
columns to exist on your table. If your table does not have these columns, set this property tofalse
.
In the Passenger
model, define its mass-assignable attributes and its relationship to Flight
using the belongsTo()
method inside app/Models/Passenger.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Passenger extends Model
{
use HasFactory;
protected $fillable = ['flight_id', 'name'];
// Each Passenger belongs to a Flight
public function flight()
{
return $this->belongsTo(Flight::class, 'flight_id');
}
}
1.5 Update Migrations with Table Structures
Edit the migration files to define the structure of flights
and passengers
tables. Ensure passengers
table includes a foreign key that references flight_id
in the flights
table.
This is our database/migrations/2024_02_17_043558_create_flights_table.php
<?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('my_flights', function (Blueprint $table) {
$table->string('flight_id')->primary(); // Assuming 'flight_id' is a string and primary
$table->string('airline');
// Add other columns as necessary
$table->timestamps(); // This adds the `created_at` and `updated_at` columns
});
}
public function down()
{
Schema::dropIfExists('my_flights');
}
};
This is our database/migrations/2024_02_17_044019_create_passengers_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('passengers', function (Blueprint $table) {
$table->id();
$table->string('flight_id'); // Match the type with the 'flight_id' in 'my_flights'
$table->string('name');
$table->timestamps();
// Foreign key constraint
$table->foreign('flight_id')
->references('flight_id')->on('my_flights')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('passengers');
}
};
1.6 Run Migrations
This command applies the migration files, creating the flights
and passengers
tables in your database with the specified structure.
php artisan migrate
1.7 Create Seeders for Dummy Data
Seeders are used to populate your database with initial data. These commands generate seeder files for both flights
and passengers
php artisan make:seeder FlightsTableSeeder
php artisan make:seeder PassengersTableSeeder
1.8 Implement Seeders
Fill the seeder files with dummy data for flights and passengers. Use the DB
facade or model factories to insert data into your database.
Here I have provided some dummy data for Flights Table inside database/seeders/FlightsTableSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Flight;
class FlightsTableSeeder extends Seeder
{
public function run()
{
Flight::create([
'flight_id' => 'FL456',
'airline' => 'Demo Airline',
]);
Flight::create([
'flight_id' => 'FL256',
'airline' => 'Dummy Airline',
]);
}
}
Similarly I have added some passenger list inside database/seeders/FlightsTableSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Flight;
class FlightsTableSeeder extends Seeder
{
public function run()
{
Flight::create([
'flight_id' => 'FL456',
'airline' => 'Demo Airline',
]);
Flight::create([
'flight_id' => 'FL256',
'airline' => 'Dummy Airline',
]);
}
}
We can also manually insert data using Laravel Tinker. Execute the following command to create a flight entry in FLight Table:
php artisan tinker
>>> use App\Models\Flight;
>>> Flight::create(['flight_id' => 'FL123', 'airline' => 'Test Airline']);
= App\Models\Flight {#4995
flight_id: "FL123",
airline: "Test Airline",
}
>>> exit
Then, try retrieving it again:
>>> App\Models\Flight::with('passengers')->get();
1.9 Seed the Database
Run the seeders to insert the data:
php artisan db:seed
1.10 Create a Controller and Define Route
The controller will contain methods to handle various requests (e.g., displaying flights and their passengers). The route defines the URL pattern that maps to these controller methods.
php artisan make:controller FlightController
In this controller inside app/Http/Controllers/FlightController.php
, using Flight::with('passengers')->get();
tells Eloquent to retrieve all flights and their associated passengers in just two queries, regardless of the number of flights. This is a significant improvement over the N+1 queries that would occur without eager loading.
<?php
namespace App\Http\Controllers;
use App\Models\Flight;
use Illuminate\Http\Request;
class FlightController extends Controller
{
public function index()
{
// Eager load passengers with flights to avoid N+1 query problem
$flights = Flight::with('passengers')->get();
return view('flights.index', ['flights' => $flights]);
}
}
Add a route to routes/web.php
to access the index
method in FlightController
.
use App\Http\Controllers\FlightController;
Route::get('/flights', [FlightController::class, 'index']);
This route configuration allows users to access the list of flights and their passengers through the /flights
URL, utilizing the optimized query in FlightController
.
1.11 Create a View
Create a Blade template to display the flights and their passengers. Use Blade syntax to loop through the flights and display each flight's details and its passengers.
mkdir resources/views/flights touch resources/views/flights/index.blade.php
Create a Blade view file to display the flights and their passengers.
<!DOCTYPE html> <html> <head> <title>Flights and Passengers</title> </head> <body> <h1>Flights and Their Passengers</h1> @foreach ($flights as $flight) <h2>{{ $flight->airline }} - Flight ID: {{ $flight->flight_id }}</h2> <ul> @foreach ($flight->passengers as $passenger) <li>{{ $passenger->name }} (Passenger ID: {{ $passenger->id }})</li> @endforeach </ul> @endforeach </body> </html>
In this view, we loop through each flight and its passengers to display them. You'll need to adjust the fields ($flight->airline
, $passenger->name
, $passenger->id
) based on your actual database schema.
1.12 Test the Application
Start the Laravel development server with:
php artisan serve
Navigate to http://localhost:8000/flights
in your web browser to see the list of flights and their passengers. This will confirm that your eager loading configuration is working correctly, optimizing your queries and efficiently loading related data.
2. Advanced Laravel Eloquent Usage
2.1 Create Model Observer
Model observers are classes that allow you to hook into various model events (creating, updating, deleting, etc.) to perform actions. For example, you might want to log every time a flight is created:
php artisan make:observer FlightObserver --model=Flight
Define Events in app/Observers/FlightObserver.php
namespace App\Observers;
use App\Models\Flight;
class FlightObserver {
public function created(Flight $flight) {
\Log::info("Flight created: {$flight->flight_id}");
}
}
Register Observer in app/Providers/AppServiceProvider.php
:
use App\Models\Flight;
use App\Observers\FlightObserver;
public function boot() {
Flight::observe(FlightObserver::class);
}
2.2 Converting Models to Arrays/JSON
Eloquent models can be easily converted to arrays or JSON, facilitating API development inside app/Http/Controllers/FlightController.php
:
namespace App\Http\Controllers;
use App\Models\Flight;
use Illuminate\Http\Request;
class FlightController extends Controller
{
public function index()
{
// Eager load passengers with flights to avoid N+1 query problem
$flights = Flight::with('passengers')->get();
// Optionally, demonstrate converting a single flight to array and JSON
// Fetch a single flight instance as an example
$singleFlight = Flight::find(1);
if ($singleFlight) {
// Convert to array
$flightArray = $singleFlight->toArray();
// Convert to JSON
$flightJson = $singleFlight->toJson();
// For demonstration, you can use 'dd' to dump the converted values to the browser
// Note: 'dd' will halt the execution, so comment these out to proceed with rendering the view
// dd($flightArray, $flightJson);
// Alternatively, pass these to the view or log them
// \Log::info('Flight as Array:', $flightArray);
// \Log::info('Flight as JSON:', $flightJson);
}
return view('flights.index', ['flights' => $flights]);
}
}
- This controller's
index
method fetches all flights along with their passengers to display them in a view, utilizing Eloquent'swith
method for eager loading to efficiently handle the N+1 query issue. - It also demonstrates fetching a single
Flight
instance by its ID (find(1)
), then converting this instance to an array and to JSON. This is particularly useful for API development, where you might need to send model data in different formats. - The
toArray()
andtoJson()
methods are used to convert theFlight
instance into an array and JSON format, respectively. These methods are part of Laravel's Eloquent and are incredibly handy for API responses or when needing to manipulate or inspect model data in these formats. - Commented-out lines with
dd()
are provided for demonstration purposes.dd()
is a Laravel helper function that dumps the variable(s) to the browser and stops execution. This can be useful for debugging but should be commented out or removed in production code.
2.3 Flight Model Enhancements
By utilizing features such as attribute casting, date mutators, accessors, and mutators, alongside defining eloquent relationships, developers can significantly reduce boilerplate code, ensure data integrity, and simplify complex data operations. We will be implementing these enhancements inside app/Models/Flight.php
Attribute Casting
Attribute casting is an automatic conversion of attributes to a common data type that occurs when you access them on your model. This feature is great for preserving the integrity of data and simplifying how it’s handled in your application.
protected $casts = [
'is_active' => 'boolean', // Automatically casts the is_active attribute to boolean
'flight_date' => 'datetime', // Converts flight_date to Carbon instance for date manipulation
];
Date Mutators
Date mutators are responsible for changing date fields into instances of Carbon, which is a PHP library for handling dates. This change allows you to easily perform operations on dates. Laravel treats created_at
and updated_at
as date instances by default, but you can make additional date fields automatically mutated as well by specifying them.
protected $dates = ['departure_time']; // Ensures departure_time is treated as a Carbon instance
Accessors and Mutators
Accessors and mutators allow you to format Eloquent attribute values when you retrieve or set them on model instances. This is useful for applying automatic data formatting rules.
Accessor: Customize how a model attribute is retrieved.
public function getAirlineAttribute($value) {
return strtoupper($value); // Always return airline names in uppercase
}
Mutator: Customize how a model attribute is set.
public function setFlightIdAttribute($value) {
$this->attributes['flight_id'] = strtoupper($value); // Store flight IDs in uppercase
}
Utilizing Eloquent Relationships
Eloquent relationships define how model entities relate to each other, simplifying the management of database relations.
One-to-Many Relationship: A flight can have many passengers. This relationship is defined in the Flight
model to easily retrieve the passengers associated with a flight.
public function passengers() {
return $this->hasMany(Passenger::class, 'flight_id');
}
This setup allows for intuitive interaction with related models, such as retrieving all passengers for a flight by accessing $flight->passengers
.
Here is my app/Models/Flight.php
file with all these changes for reference:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
use HasFactory;
// use SoftDeletes;
protected $table = 'my_flights';
protected $primaryKey = 'flight_id';
public $incrementing = false;
protected $keyType = 'string';
public $timestamps = true;
protected $fillable = ['flight_id', 'airline']; // Add attributes you want to be mass-assignable
protected $casts = [
'is_active' => 'boolean',
'flight_date' => 'datetime:Y-m-d',
];
protected $dates = ['departure_time'];
// Accessor
public function getFlightIdAttribute($value) {
return strtoupper($value);
}
// Mutator
public function setFlightIdAttribute($value) {
$this->attributes['flight_id'] = strtolower($value);
}
// A Flight has many Passengers
public function passengers()
{
return $this->hasMany(Passenger::class, 'flight_id');
}
}
2.4 Eloquent Collections
Eloquent Collections provide a fluent, convenient wrapper for working with arrays of models. You can create a new method in your FlightController
to specifically showcase these Eloquent Collection methods.
use App\Models\Flight;
use Illuminate\Http\Request;
public function collectionsDemo(Request $request)
{
// Initialize variables
$containsTestAirline = false;
$specificFlight = null;
$flights = collect([]);
// Get flight_id from request query parameters
$requestedFlightId = $request->query('flight_id');
if ($requestedFlightId) {
// Fetch a specific flight by flight_id, including passengers
$specificFlight = Flight::with('passengers')->where('flight_id', $requestedFlightId)->first();
// Check if the specific flight exists and if it contains a specific airline
if ($specificFlight) {
$containsTestAirline = $specificFlight->airline == 'Test Airline';
$flights = collect([$specificFlight]); // Wrap the specific flight in a collection for consistency
}
} else {
// Fetch all flights with passengers to avoid N+1 query problem
$flights = Flight::with('passengers')->get();
// Check if any flight in the collection contains a specific airline
$containsTestAirline = $flights->contains('airline', 'Test Airline');
}
// Prepare the flights data, ensuring consistency in the output format
$flightsData = $flights->map(function ($flight) {
return [
'flight_id' => $flight->flight_id,
'airline' => $flight->airline,
'passengers' => $flight->passengers->toArray(), // Convert related passengers to an array
'created_at' => $flight->created_at,
'updated_at' => $flight->updated_at,
];
});
return response()->json([
'specificFlight' => $specificFlight ? $specificFlight->toArray() : null,
'containsTestAirline' => $containsTestAirline,
'flights' => $flightsData
]);
}
In the provided collectionsDemo
method, several Laravel Eloquent methods are utilized to interact with the database and manipulate the collection of models. Here's a summary of the Eloquent methods used:
with('passengers')
: This method is used for eager loading the relatedpassengers
models alongside theFlight
models to prevent the N+1 query problem. It ensures that all related passengers are loaded in a single query when fetching flights.where('flight_id', $requestedFlightId)
: Applied to theFlight
model, this method filters the flights to find the one with the specifiedflight_id
. It's used to retrieve a specific flight based on the request parameter.first()
: After applying thewhere
clause,first()
fetches the first result of the query, returning a single model instance ornull
if no matching model is found.get()
: This method retrieves all records that match the query constraints. Without any constraints, it fetches all instances of the model from the database.contains('airline', 'Test Airline')
: Used on a collection, this method checks if any of the models in the collection have anairline
attribute equal to "Test Airline".whereIn('flight_id', $flightIds)
: This collection method filters the models based on whether theirflight_id
is in the specified array of IDs.map()
: A collection method that applies a callback to each item in the collection and returns a new collection with the items returned by the callback. In this context, it's used to format the flights and their passengers' data.toArray()
: Converts the model or collection into a plain PHP array. This is particularly useful when preparing data for JSON responses.
2.5 Register a Route for the Demonstration
To access this demonstration through your web application, you need to define a route that points to this new method. Open your routes/web.php
file and add a new route:
Route::get('/flights/collections-demo', [FlightController::class, 'collectionsDemo']);
This route enables you to navigate to /flights/collections-demo
in your browser to see the results of the Eloquent Collections demonstrations.
2.6 Testing the Implementation
I have created and added some more flight details into the table for the demonstration using tinker as showed earlier. Start the application web server if not running already:
php artisan serve
Access this endpoint from a web browser or a tool like Postman with a query parameter.
To test fetching all flights and checking for the presence of "Test Airline", simply access /flights/collections-demo
without any query parameters.
To filter by a specific flight_id
, append the query parameter like /flights/collections-demo?flight_id=FL123
. This will return details for the flight "FL123", if it exists, along with its passengers.
The collectionsDemo
method showcases Laravel Eloquent's prowess in efficiently fetching and manipulating database records. It demonstrates eager loading, dynamic filtering based on request parameters, and collection manipulation techniques such as contains
, whereIn
, and map
. This method elegantly handles conditional data retrieval and formatting, exemplifying Eloquent's capability to streamline complex database interactions and data presentation in Laravel applications.
With the collectionsDemo
method, you can see how Laravel Eloquent can fetch and manipulate database records with ease. Eager loading? Check. Dynamic filtering? Check. The ability to use collection manipulation techniques like contains, whereIn
, and map
? Absolutely. This method has a way of handling conditional data retrieval and formatting that just makes sense. And it does that while showcasing Eloquent’s power in simplifying complex database interactions and data presentation in Laravel applications.
This article takes a deep dive into Laravel's Eloquent ORM, and showcases how crucial it is to Laravel applications for powerful database interaction. From model generation and migration handling, to query optimization and CRUD operations, it covers all the fundamentals such as model conventions; advanced features like attribute casting and relationships; and the use of Eloquent Collections. The guide really emphasizes on how Eloquent simplifies complex data management tasks by being super efficient.