Skip to content

Relations Definition

lpachecob edited this page Mar 24, 2026 · 1 revision

🔗 Relations Definition: Database Relationships

BOUNDLY supports all major relationship types through declarative attributes. No manual pivot tables or foreign key configuration needed.


📍 Location

Infrastructure\FrameworkCore\Attributes\Relations\

One-to-Many (N→1)

#[BelongsTo]

Declares that this entity belongs to another. Automatically creates the foreign key column.

Parameter Type Default Description
relatedEntity string Required The parent entity class name
foreignKey string '' FK column name (auto-generated if empty)
nullable bool true Allow NULL when no parent exists

Use Case: When an entity has a reference to another entity (e.g., a Post belongs to a User).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column};
use Infrastructure\FrameworkCore\Attributes\Relations\BelongsTo;

#[Entity(table: 'posts', resource: 'posts')]
class Post extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 255)]
    private string $title;

    #[BelongsTo(relatedEntity: User::class)]
    private array $author;
}

Generated Schema:

CREATE TABLE posts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    user_id BIGINT UNSIGNED NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

API Behavior:

  • GET /api/posts includes the related author
  • POST /api/posts accepts user_id in payload

One-to-Many Inverse

#[HasMany]

Declares the inverse side of a one-to-many relationship. No schema changes needed (handled by the BelongsTo side).

Parameter Type Default Description
relatedEntity string Required The child entity class name
foreignKey string '' The FK column on the child entity

Use Case: When an entity can have multiple related entities (e.g., a User has many Posts).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\HasMany;

#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 150)]
    private string $name;

    #[HasMany(relatedEntity: Post::class)]
    private array $posts;
}

One-to-One Inverse

#[HasOne]

Declares a one-to-one relationship where this entity has one related entity.

Parameter Type Default Description
relatedEntity string Required The related entity class name
foreignKey string '' The FK column on the related entity

Use Case: When an entity has exactly one related entity (e.g., a User has one Profile).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\HasOne;

#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 150)]
    private string $name;

    #[HasOne(relatedEntity: Profile::class)]
    private array $profile;
}

Many-to-Many

#[ManyToMany]

Creates a many-to-many relationship with an automatic pivot table.

Parameter Type Default Description
relatedEntity string Required The related entity class name
pivotTable string '' Pivot table name (auto-generated if empty)
foreignPivotKey string '' FK on pivot pointing to this entity
relatedPivotKey string '' FK on pivot pointing to related entity

Use Case: When entities can have multiple relationships with each other (e.g., Users have many Roles, Roles have many Users).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\ManyToMany;

#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 150)]
    private string $name;

    #[ManyToMany(relatedEntity: Role::class)]
    private array $roles;
}

Generated Pivot Table:

CREATE TABLE role_user (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    role_id BIGINT UNSIGNED NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY role_user_unique (user_id, role_id)
);

Custom Pivot Table Name:

#[ManyToMany(
    relatedEntity: Role::class,
    pivotTable: 'user_permissions',
    foreignPivotKey: 'user_id',
    relatedPivotKey: 'permission_id'
)]
private array $permissions;

Using in API:

// POST /api/users
{
  "name": "John Doe",
  "roles": [1, 2, 3]
}

// GET /api/users?include=roles
{
  "id": 1,
  "name": "John Doe",
  "roles": [
    {"id": 1, "name": "Admin"},
    {"id": 2, "name": "Editor"},
    {"id": 3, "name": "Viewer"}
  ]
}

Polymorphic Relationships

Overview

Polymorphic relationships allow a model to belong to more than one other model on a single association. For example, you might have a Comment model that can belong to either a Post or a Video model.

#[MorphMany]

One entity can be the parent of multiple other entity types (1→N polymorphic).

Parameter Type Default Description
relatedEntity string Required The child entity class name
relation string Required The polymorphic relation name

Use Case: When an entity can have multiple comments of different types (e.g., Posts, Videos, Products all have comments).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\MorphMany;

#[Entity(table: 'posts', resource: 'posts')]
class Post extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 255)]
    private string $title;

    #[MorphMany(relatedEntity: Comment::class, relation: 'commentable')]
    private array $comments;
}

Child Entity (Comment):

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\MorphTo;

#[Entity(table: 'comments', resource: 'comments')]
class Comment extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'text')]
    private string $body;

    #[MorphTo]
    private array $commentable;
}

Generated Schema:

CREATE TABLE comments (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    body TEXT,
    commentable_id BIGINT UNSIGNED NULL,
    commentable_type VARCHAR(255) NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL
);

#[MorphOne]

One polymorphic relationship (1→1 polymorphic).

Parameter Type Default Description
relatedEntity string Required The child entity class name
relation string Required The polymorphic relation name

Use Case: When an entity can have one image of different types (e.g., Users, Posts, Products all have one featured image).

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\MorphOne;

#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 150)]
    private string $name;

    #[MorphOne(relatedEntity: Image::class, relation: 'imageable')]
    private array $avatar;
}

#[MorphTo]

The child side of a polymorphic relationship. Automatically creates {name}_id and {name}_type columns.

Parameter Type Default Description
name string null Morph name (defaults to property name)

Use Case: Define on the child entity to specify which entities can own it.

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Relations\MorphTo;

#[Entity(table: 'images', resource: 'images')]
class Image extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'string', length: 500)]
    private string $url;

    #[MorphTo(name: 'imageable')]
    private array $imageable;
}

🔄 Eager Loading with Include

Load relationships efficiently using dot notation for nested relations:

# Single relation
GET /api/posts?include=author

# Multiple relations
GET /api/users?include=profile,posts

# Nested relations (posts → comments → author)
GET /api/users?include=posts.comments.author

# Polymorphic relations
GET /api/posts?include=comments
GET /api/videos?include=comments

Performance: BOUNDLY automatically optimizes N+1 queries into efficient WHERE IN (...) calls.


📋 Relationship Quick Reference

Relationship Parent Attribute Child Attribute Schema Changes
One-to-Many #[BelongsTo] #[HasMany] FK on child
One-to-One #[BelongsTo] #[HasOne] FK on child
Many-to-Many #[ManyToMany] #[ManyToMany] Pivot table created
Polymorphic (1→N) #[MorphMany] #[MorphTo] FK + Type on child
Polymorphic (1→1) #[MorphOne] #[MorphTo] FK + Type on child

Next Step: Validation-Attributes

Clone this wiki locally