Models & Relationships

Create ORM models with attribute-based relationships using PHP 8.5

Creating a Model

Models in Larafony extend the Model base class and use PHP 8.5 property hooks for clean, type-safe data access.

<?php

declare(strict_types=1);

namespace App\Models;

use Larafony\Framework\Database\ORM\Model;

class User extends Model
{
    public string $table { get => 'users'; }

    public array $fillable = ['name', 'email'];

    public ?string $name {
        get => $this->name ?? null;
        set {
            $this->name = $value;
            $this->markPropertyAsChanged('name');
        }
    }

    public ?string $email {
        get => $this->email ?? null;
        set {
            $this->email = $value;
            $this->markPropertyAsChanged('email');
        }
    }
}
Property Hooks: PHP 8.5 property hooks allow you to define getter and setter logic directly on properties. Call markPropertyAsChanged() in setters to track changes for saving.

BelongsTo Relationship

A BelongsTo relationship defines that a model belongs to another model. For example, a Note belongs to a User.

<?php

namespace App\Models;

use Larafony\Framework\Database\ORM\Model;
use Larafony\Framework\Database\ORM\Attributes\BelongsTo;

class Note extends Model
{
    public string $table { get => 'notes'; }

    public array $fillable = ['title', 'content', 'user_id'];

    // BelongsTo: Note belongs to User
    #[BelongsTo(
        related: User::class,
        foreign_key: 'user_id',
        local_key: 'id'
    )]
    public ?User $user {
        get => $this->relations->getRelation('user');
    }
}

Access the relationship like a property:

$note = Note::query()->find(1);
echo $note->user->name; // Lazy-loaded automatically

HasMany Relationship

A HasMany relationship defines that a model has many related models. For example, a User has many Notes.

<?php

namespace App\Models;

use Larafony\Framework\Database\ORM\Model;
use Larafony\Framework\Database\ORM\Attributes\HasMany;

class User extends Model
{
    public string $table { get => 'users'; }

    // HasMany: User has many Notes
    #[HasMany(
        related: Note::class,
        foreign_key: 'user_id',
        local_key: 'id'
    )]
    public array $notes {
        get => $this->relations->getRelation('notes');
    }
}

Access the collection:

$user = User::query()->find(1);

foreach ($user->notes as $note) {
    echo $note->title;
}

BelongsToMany Relationship

A BelongsToMany relationship defines a many-to-many relationship through a pivot table. For example, a Note belongs to many Tags, and a Tag belongs to many Notes.

<?php

namespace App\Models;

use Larafony\Framework\Database\ORM\Model;
use Larafony\Framework\Database\ORM\Attributes\BelongsToMany;

class Note extends Model
{
    public string $table { get => 'notes'; }

    // BelongsToMany: Note belongs to many Tags
    #[BelongsToMany(
        related: Tag::class,
        pivot_table: 'note_tag',
        foreign_pivot_key: 'note_id',
        related_pivot_key: 'tag_id'
    )]
    public array $tags {
        get => $this->relations->getRelation('tags');
    }

    /**
     * Attach tags to this note
     */
    public function attachTags(array $tagIds): void
    {
        $relation = $this->relations->getRelationInstance('tags');
        $relation->attach($tagIds);
    }

    /**
     * Detach tags from this note
     */
    public function detachTags(array $tagIds): void
    {
        $relation = $this->relations->getRelationInstance('tags');
        $relation->detach($tagIds);
    }
}

Working with many-to-many relationships:

$note = Note::query()->find(1);

// Access tags
foreach ($note->tags as $tag) {
    echo $tag->name;
}

// Attach new tags
$note->attachTags([1, 2, 3]);

// Detach tags
$note->detachTags([2]);

Complete Example

Here's a complete model with all three relationship types:

<?php

declare(strict_types=1);

namespace App\Models;

use Larafony\Framework\Database\ORM\Model;
use Larafony\Framework\Database\ORM\Attributes\{
    BelongsTo,
    HasMany,
    BelongsToMany
};

class Note extends Model
{
    public string $table { get => 'notes'; }

    public array $fillable = ['title', 'content', 'user_id'];

    public ?string $title {
        get => $this->title ?? null;
        set {
            $this->title = $value;
            $this->markPropertyAsChanged('title');
        }
    }

    public ?string $content {
        get => $this->content ?? null;
        set {
            $this->content = $value;
            $this->markPropertyAsChanged('content');
        }
    }

    public ?int $user_id {
        get => $this->user_id ?? null;
        set {
            $this->user_id = $value;
            $this->markPropertyAsChanged('user_id');
        }
    }

    // BelongsTo: Note belongs to User
    #[BelongsTo(
        related: User::class,
        foreign_key: 'user_id',
        local_key: 'id'
    )]
    public ?User $user {
        get => $this->relations->getRelation('user');
    }

    // HasMany: Note has many Comments
    #[HasMany(
        related: Comment::class,
        foreign_key: 'note_id',
        local_key: 'id'
    )]
    public array $comments {
        get => $this->relations->getRelation('comments');
    }

    // BelongsToMany: Note belongs to many Tags
    #[BelongsToMany(
        related: Tag::class,
        pivot_table: 'note_tag',
        foreign_pivot_key: 'note_id',
        related_pivot_key: 'tag_id'
    )]
    public array $tags {
        get => $this->relations->getRelation('tags');
    }

    public function attachTags(array $tagIds): void
    {
        $relation = $this->relations->getRelationInstance('tags');
        $relation->attach($tagIds);
    }
}

Query Builder

Access the query builder through the static query() method:

// Find by ID
$note = Note::query()->find(1);

// Get all records
$notes = Note::query()->get();

// Where clause
$notes = Note::query()
    ->where('user_id', '=', 1)
    ->get();

// Order and limit
$notes = Note::query()
    ->where('published', '=', true)
    ->orderBy('created_at', 'DESC')
    ->limit(10)
    ->get();

// First record
$note = Note::query()
    ->where('slug', '=', 'hello-world')
    ->first();

Creating and Updating

Use the fill() method for mass assignment:

// Create new record
$note = new Note()->fill([
    'title' => 'My Note',
    'content' => 'Note content here',
    'user_id' => 1,
]);
$note->save();

// Update existing record
$note = Note::query()->find(1);
$note->title = 'Updated Title';
$note->save();

Next Steps