Efficiently managing database queries is crucial for application performance. To simplify it, I made a nice CodeIgniter 4 library that provides two techniques for loading related data: eager loading and lazy loading.

composer require michalsn/codeigniter-nested-model

These approaches allow developers to fetch related models in an efficient and intuitive way. Here’s a comprehensive guide to understanding and using these techniques.

Eager Loading

Eager loading fetches related data in advance, minimizing the number of database queries. To implement eager loading, you must define the relations between your models.

Defining Relations

In the following example, we have a UserModel with a one-to-one relation to ProfileModel. The HasRelations trait is used to define these relations.

class UserModel extends Model
{
    use HasRelations;

    public function initialize()
    {
        $this->initRelations();
    }

    public function profile(): Relation
    {
        return $this->hasOne(ProfileModel::class);
    }
}

Fetching Data

To eagerly load data, use the with() method and specify the relation:

$users = model(UserModel::class)->with('profile')->findAll();

This code performs two queries: one to fetch all users and another to fetch all profiles associated with these users.

Writing with Relations

Eager loading also supports saving related models. For simple relations like hasOne or hasMany, you can save the entire object with its relations:

$userModel = model(UserModel::class);
$user      = $userModel->with('profile')->find(1);

$user->profile->favorite_pet = 'Cat';

$userModel->with('profile')->save($user);

Using Transactions

When saving objects with relations, it’s recommended to use the useTransactions() method. This ensures that all changes are rolled back if something goes wrong:

$userModel->with('profile')->useTransactions()->save($user);

Lazy Loading

Lazy loading fetches related data only when it is accessed. This requires your model to use an Entity as the $returnType.

Defining Relations for Lazy Loading

The setup for lazy loading is similar to eager loading but requires an Entity class. The UserModel must use the HasRelations trait, and the $returnType must be set to an entity class:

class UserModel extends Model
{
    use HasRelations;

    protected $returnType = User::class;

    public function initialize()
    {
        $this->initRelations();
    }

    public function profile(): Relation
    {
        return $this->hasOne(ProfileModel::class);
    }
}

Creating the Entity

The entity class must use the HasLazyRelations trait:

class User extends Entity
{
    use HasLazyRelations;
}

Fetching Data

With lazy loading, data is retrieved on demand when you access the relation property:

$users = model(UserModel::class)->findAll();
foreach ($users as $user) {
    d($user->profile);
}

This approach performs an n+1 query pattern: one query to fetch all users and one additional query for each related profile.

Choosing Between Eager and Lazy Loading

  • Eager Loading: Best when you know in advance which relations you’ll need. Reduces the number of queries by preloading data.
  • Lazy Loading: Useful when you don’t know which relations will be accessed. However, it can lead to multiple queries and potential performance issues.

Both techniques offer flexibility and help optimize database interactions in CodeIgniter 4. By understanding their strengths and appropriate use cases, you can build more efficient and maintainable applications.

For more information, see the project documentation: https://michalsn.github.io/codeigniter-nested-model/