Skip to content

One to many / Has Many

A one-to-many relationship where one model is associated with multiple instances of another model.

When to Use

Use HasMany when a parent model can have multiple related records. Common examples:

  • User has many Posts
  • Category has many Products
  • Author has many Books

Database Structure

users
-----
id           (primary key)
name
email

posts
-----
id           (primary key)
user_id      (foreign key)
title
content
created_at

Defining the Relation

use Michalsn\CodeIgniterRelations\Relations\HasMany;
use Michalsn\CodeIgniterRelations\Traits\HasRelations;

class UserModel extends Model
{
    use HasRelations;

    protected $table = 'users';
    protected $returnType = User::class;

    public function posts(): HasMany
    {
        return $this->hasMany(PostModel::class);
    }
}

Custom Foreign Keys

By default, the foreign key is assumed to be {parent_table_singular}_id (e.g., user_id). You can customize it:

public function posts(): HasMany
{
    return $this->hasMany(PostModel::class, 'custom_user_id');
}

Reading Data

Eager Loading

// Load user with posts
$user = model(UserModel::class)->with('posts')->find(1);
foreach ($user->posts as $post) {
    echo $post->title;
}

// Load multiple users with posts
$users = model(UserModel::class)->with('posts')->findAll();
foreach ($users as $user) {
    foreach ($user->posts as $post) {
        echo "{$user->name}: {$post->title}";
    }
}

Lazy Loading

$user = model(UserModel::class)->find(1);
foreach ($user->posts as $post) {
    echo $post->title; // Posts are loaded automatically when accessed
}

With Query Constraints

// Only published posts
$user = model(UserModel::class)
    ->with('posts', fn($model) => $model->where('published', 1))
    ->find(1);

// Recent posts ordered by date
$user = model(UserModel::class)
    ->with('posts', fn($model) => $model->orderBy('created_at', 'DESC')->limit(5))
    ->find(1);

Writing Data

Creating a Single Record

Use save() to create a new related record:

$user = model(UserModel::class)->find(1);

// Save with array
$user->posts()->save([
    'title' => 'My First Post',
    'content' => 'This is the content...',
]);

// Save with entity
$post = new Post();
$post->title = 'My Second Post';
$post->content = 'More content...';
$user->posts()->save($post);

The save() method automatically sets the foreign key (user_id) and either inserts a new record or updates an existing one (if ID is present).

Creating Multiple Records

Use saveMany() to create multiple related records at once:

$user = model(UserModel::class)->find(1);

// Save multiple posts with arrays
$user->posts()->saveMany([
    ['title' => 'Post 1', 'content' => 'Content 1'],
    ['title' => 'Post 2', 'content' => 'Content 2'],
    ['title' => 'Post 3', 'content' => 'Content 3'],
]);

// Save with entities
$post1 = new Post(['title' => 'Post 4', 'content' => 'Content 4']);
$post2 = new Post(['title' => 'Post 5', 'content' => 'Content 5']);
$user->posts()->saveMany([$post1, $post2]);

Updating Existing Records

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

// Update individual post
$post = $user->posts[0];
$post->title = 'Updated Title';
$post->save();

// Or use save() with ID present for updates
$user->posts()->save([
    'id' => 5,
    'title' => 'Updated via save()',
    'content' => 'Updated content',
]);

Available Methods

Method Description
save($data) Create or update a single related record
saveMany($data, $useTransaction = true) Create or update multiple related records

Transaction Control

By default, saveMany() uses a database transaction. If any record fails validation, all changes are rolled back:

// Default: uses transaction
$user->posts()->saveMany([...]);

// Disable transaction for partial success
$user->posts()->saveMany([...], useTransaction: false);