Simplifying Database Operations with Eloquent ORM
From writing raw SQL to model-based access: a practical look at Laravel's Eloquent ORM and how it fits into day-to-day development.
Before switching to Laravel, I handled most of my database work by writing SQL by hand. I started with mysqli_query(), eventually moved to PDO, but every query still meant writing the same prepare, bindParam, execute cycle over and over. It was repetitive and error-prone.
Laravel’s Eloquent ORM approaches this from a completely different angle. I’ve been using it for just over a month now, and the difference in day-to-day work is noticeable.
What is an ORM?
An ORM (Object-Relational Mapper) is the layer that represents database tables as objects on the application side. Instead of talking directly to a table, you work with a PHP object — the ORM handles the query work behind the scenes.
Eloquent is Laravel’s implementation of this layer. You define a model class for each database table.
Defining a Model
app/models/User.php:
<?php
class Kullanici extends Eloquent
{
protected $table = 'kullanicilar';
}
That’s it. If you leave out the $table property, Eloquent will derive the table name from the class name automatically: Kullanici → kullanicis (using English pluralization rules). If you’re using non-English table names, it’s safer to specify $table explicitly.
Reading Records
// All users
$kullanicilar = Kullanici::all();
// Single record by ID
$kullanici = Kullanici::find(1);
// Conditional query
$aktifler = Kullanici::where('durum', '=', 'aktif')->get();
// First matching record
$yonetici = Kullanici::where('rol', 'admin')->first();
Methods like all(), find(), and where() build the SQL query in the background. You work with PHP methods throughout.
Creating and Updating Records
// New record
$kullanici = new Kullanici();
$kullanici->ad = 'Ahmet';
$kullanici->email = '[email protected]';
$kullanici->save();
// Or in one line with create() (mass assignment protection must be enabled)
Kullanici::create([
'ad' => 'Mehmet',
'email' => '[email protected]'
]);
// Updating
$kullanici = Kullanici::find(5);
$kullanici->email = '[email protected]';
$kullanici->save();
When using the create() method, you need to define the $fillable property on the model — this specifies which fields are open to mass assignment:
class Kullanici extends Eloquent
{
protected $table = 'kullanicilar';
protected $fillable = ['ad', 'email'];
}
Any field not listed in $fillable will not be saved, even if it’s passed in directly from user input. This is a simple but important protection against attacks where someone adds a field like role=admin to a form submission to escalate their privileges. You can also set $guarded = [] to open all fields, but starting with everything closed and adding fields as needed is a much safer habit.
Deleting Records
$kullanici = Kullanici::find(3);
$kullanici->delete();
// Or directly
Kullanici::destroy(3);
Query Chaining
One of the things I appreciate about Eloquent is the ability to chain methods:
$sonuc = Kullanici::where('durum', 'aktif')
->orderBy('olusturuldu_at', 'desc')
->take(10)
->get();
This fetches the 10 most recently created active users, sorted from newest to oldest. Writing the equivalent in raw SQL would be longer and more prone to mistakes.
Relationships
Defining relationships between tables at the model level is one of Eloquent’s strongest features. Here’s a basic example:
class Kullanici extends Eloquent
{
public function yorumlar()
{
return $this->hasMany('Yorum');
}
}
class Yorum extends Eloquent
{
public function kullanici()
{
return $this->belongsTo('Kullanici');
}
}
After defining these relationships:
$kullanici = Kullanici::find(1);
$yorumlar = $kullanici->yorumlar; // all comments by this user
$yorum = Yorum::find(10);
$yazanKisi = $yorum->kullanici; // the user who wrote the comment
You access related data just like a property — a JOIN query runs behind the scenes.
firstOrCreate and updateOrCreate
Two methods I find myself reaching for frequently in daily work are those that check whether a record already exists, and create it if it doesn’t. Doing this manually looks like:
// Manual: find first, create if not found
$kullanici = Kullanici::where('email', $email)->first();
if (!$kullanici) {
$kullanici = Kullanici::create(['email' => $email, 'ad' => $ad]);
}
Eloquent’s firstOrCreate method reduces this to a single call:
$kullanici = Kullanici::firstOrCreate(
['email' => $email], // Search criteria
['ad' => $ad] // Fields to set if not found
);
updateOrCreate takes it a step further — it creates the record if it doesn’t exist, or updates it if it does. This is invaluable when working with import jobs or syncing data from external services.
One Thing to Keep in Mind
Using an ORM doesn’t make SQL knowledge unnecessary. It’s important to understand what kinds of queries are being generated under the hood. In particular, when fetching related data inside loops, you need to watch out for the N+1 query problem.
Here’s how N+1 happens: you fetch 10 users (1 query), then fetch each user’s comments separately (10 more queries) — 11 queries in total. When the user count reaches 100, that’s 101 queries and the page starts to crawl.
Eloquent’s with() method solves this by eager loading related data in a single query. That’s a separate topic in itself, but once you start working with relationships, learning this concept becomes unavoidable.
For now, Eloquent makes day-to-day work considerably less tiring than writing raw SQL. Once the model is defined, everything else flows naturally.
At some point while using an ORM, the question “where is the real SQL actually being written?” comes up. In Laravel, you can inspect the queries Eloquent generates using DB::listen() or dd(DB::getQueryLog()). Keeping this open in your development environment is a good habit for catching performance issues early. You’ll spot unexpected JOIN counts or subqueries in a single query and reconsider how you’ve structured the model. An ORM abstracts you away from SQL, but when you truly respect the tool, you want to understand what’s happening beneath the abstraction.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.