Understanding ORMs

When developing applications that interact with databases, one of the challenges developers face is mapping the object-oriented programming (OOP) paradigm with relational database systems. This is where Object-Relational Mapping (ORM) comes into play. In this post, we’ll delve into what an ORM is, its benefits, and how it works, with examples using Laravel’s Eloquent ORM.

What is an ORM?

ORM, or Object-Relational Mapping, is a technique that allows developers to interact with a database using an object-oriented programming language. Instead of writing raw SQL queries to perform CRUD (Create, Read, Update, Delete) operations, developers can use ORM to interact with the database using the programming language’s syntax and constructs.

In essence, ORM acts as a bridge between the application and the database, mapping database tables to classes and records to objects.

How Does ORM Work?

ORM works by defining classes in the programming language that represent tables in the database. Each instance of a class corresponds to a row in the database table. Attributes of the class map to columns in the table, and methods on the class allow for database operations.

For example, consider a simple users table in a database:

| id  | name      | email             |
| --- | --------- | ----------------- |
| 1   | John Doe  | [email protected]  |
| 2   | Jane Doe  | [email protected]  |

Using an ORM, you could represent this table with a User class:

class User {
    public $id;
    public $name;
    public $email;
}

With an ORM, you can perform database operations using methods on this class instead of writing raw SQL queries.

Benefits of Using an ORM

Simplicity: ORM provides an easy-to-use, object-oriented API for interacting with the database, reducing the need for complex SQL queries.

Database Abstraction: ORMs abstract the database system, allowing you to switch between different databases without changing your code.

Security: ORMs help prevent SQL injection attacks by using parameterized queries.

Productivity: With ORM, developers can write less boilerplate code and focus on business logic, enhancing productivity.

Maintainability: Code is easier to maintain and understand, as ORM often follows object-oriented principles and patterns.

Basic CRUD Operations with Eloquent

Let’s explore some basic CRUD operations using Eloquent to illustrate how ORMs work in practice.

Setting Up Eloquent

To get started with Eloquent, you need to define a model for your database table. Each model corresponds to a table in your database.

Consider the following users table:

| id  | name      | email             |
| --- | --------- | ----------------- |
| 1   | John Doe  | [email protected]  |
| 2   | Jane Doe  | [email protected]  |

To work with this table using Eloquent, create a User model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // You can specify the table name if it's different from the plural of the class name
    // protected $table = 'users';

    // Specify the attributes that are mass assignable
    protected $fillable = ['name', 'email'];
}

Creating Records

You can create a new record in the database using the create method:

use App\Models\User;

// Creating a new user
$user = User::create([
    'name' => 'Alice Smith',
    'email' => '[email protected]'
]);

Eloquent will automatically insert a new row into the users table.

Alternatively, you can use the save method:

$user = new User();
$user->name = 'Bob Johnson';
$user->email = '[email protected]';
$user->save();

Reading Records

To retrieve records from the database, you can use Eloquent’s fluent query builder:

Retrieving All Records

$users = User::all();

foreach ($users as $user) {
    echo $user->name . ' - ' . $user->email;
}

Retrieving a Single Record

You can retrieve a single record using the find method or the where method:

// Find by primary key
$user = User::find(1);

// Find by specific attribute
$user = User::where('email', '[email protected]')->first();

echo $user->name . ' - ' . $user->email;

Updating Records

Updating records is straightforward with Eloquent. You can use the update method on a model instance:

$user = User::find(1);
$user->name = 'Johnathan Doe';
$user->save();

Or you can use the update method on the query builder:

User::where('email', '[email protected]')->update(['name' => 'Jane Smith']);

Deleting Records

Deleting records can be done using the delete method:

// Delete a specific user
$user = User::find(1);
$user->delete();

// Delete using query builder
User::where('name', 'Bob Johnson')->delete();

Advanced Features of Eloquent

Eloquent offers several advanced features that make working with databases even more powerful and intuitive:

Relationships

Eloquent provides support for defining relationships between models, such as:

  • One-to-One: A user has one profile.
  • One-to-Many: A post has many comments.
  • Many-to-Many: A user belongs to many roles.

Here’s an example of a one-to-many relationship:

// User model
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// Post model
class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

You can access related models using dynamic properties:

$user = User::find(1);
foreach ($user->posts as $post) {
    echo $post->title;
}

Eager Loading

Eloquent supports eager loading, which allows you to load related models efficiently:

// Load users and their posts in a single query
$users = User::with('posts')->get();

foreach ($users as $user) {
    echo $user->name . ' has ' . $user->posts->count() . ' posts.';
}

Query Scopes

Eloquent provides query scopes to define common query logic that can be reused. There are two types of scopes: global and local.

Local Scopes

Local scopes allow you to define common query conditions inside your model:

class User extends Model
{
    public function scopeActive($query)
    {
        return $query->where('active', 1);
    }
}

// Usage of local scope
$activeUsers = User::active()->get();

Global Scopes

Global scopes apply conditions to all queries for a particular model:

use Illuminate\Database\Eloquent\Builder;

class User extends Model
{
    protected static function booted()
    {
        static::addGlobalScope('active', function (Builder $builder) {
            $builder->where('active', 1);
        });
    }
}

Accessors and Mutators

Accessors and mutators allow you to manipulate data before it’s retrieved or saved to the database.

Accessors

Accessors format attributes when retrieving them from the database:

class User extends Model
{
    public function getFullNameAttribute()
    {
        return $this->name . ' (' . $this->email . ')';
    }
}

// Usage
$user = User::find(1);
echo $user->full_name; // Outputs: John Doe ([email protected])

Mutators

Mutators modify attribute values before they are saved to the database:

class User extends Model
{
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = bcrypt($value);
    }
}

// Setting the password
$user = User::find(1);
$user->password = 'newpassword123';
$user->save();