Skip to content

Entity Definition

lpachecob edited this page Mar 26, 2026 · 4 revisions

🧬 Entity Definition: Schema Attributes Reference

In BOUNDLY, your domain entities are your database schema. No migrate files needed. Just attributes.


πŸ“ Location

Infrastructure\FrameworkCore\Attributes\Schema\

πŸ” #[Entity]

Required for every persistent class. Marks a class as a database entity.

Parameter Type Default Description
table string Required The database table name
resource string null The API resource name (plural). Defaults to table name
morphName string null Morph map alias for polymorphic relations
connection string 'mysql' Database connection to use

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\Entity;

#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot { ... }

πŸ’  #[Column]

Defines a database column with its properties.

Parameter Type Default Description
type string 'string' Column type (see below)
length int 255 Max length for string types
nullable bool false Allow NULL values
default mixed null Default value
unique bool false Add unique index
roles array [] Role-based visibility control

Supported Column Types:

Type SQL Notes
string VARCHAR Variable length
text TEXT Unlimited text
integer INT Whole numbers
bigint BIGINT Large integers (auto-mapped to Laravel's bigInteger)
boolean TINYINT(1) 0 or 1
decimal(p,s) DECIMAL Precise numbers (e.g., 'decimal(10,2)')
date DATE YYYY-MM-DD
datetime DATETIME Date and time
timestamp TIMESTAMP Unix timestamp
json JSON JSON data
uuid CHAR(36) UUID string

Note: When using #[Column] attributes, if you also use #[Auditable], ensure you don't duplicate created_by or updated_by columns. The framework will auto-add these audit columns only if they are not already defined as #[Column] attributes.

Example:

#[Column(type: 'string', length: 150, nullable: true, default: 'N/A')]
private string $nickname;

#[Column(type: 'decimal(10,2)', default: 0.00)]
private string $price;

#[Column(type: 'json')]
private array $metadata;

πŸ†” #[Id]

Marks a property as the primary key. By default, it creates an auto-incrementing BIGINT.

Parameter Type Default Description
autoIncrement bool true Auto-increment the ID

Example:

#[Id]
private int $id;

πŸ”‘ #[PrimaryKey]

Extended primary key configuration. Use this when you need custom column types or non-auto-incrementing keys (e.g., UUIDs).

Parameter Type Default Description
autoIncrement bool true Auto-increment the ID
type string 'bigint' Column type (bigint, int, uuid)

Use Case: When using UUIDs or string-based primary keys.

Example:

// UUID Primary Key
#[PrimaryKey(autoIncrement: false, type: 'uuid')]
private string $id;

// Custom BIGINT Primary Key
#[PrimaryKey(autoIncrement: true, type: 'bigint')]
private int $id;

πŸ”— #[ForeignKey]

Defines a foreign key constraint with cascade behavior.

Parameter Type Default Description
entity string Required Related entity class name
column string null FK column name (auto-generated if null)
onDelete string 'RESTRICT' Action on delete (CASCADE, SET NULL, RESTRICT, NO ACTION)
onUpdate string 'CASCADE' Action on update (CASCADE, SET NULL, RESTRICT, NO ACTION)

Example:

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, ForeignKey};
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;

    #[ForeignKey(entity: User::class, onDelete: 'CASCADE')]
    #[BelongsTo(relatedEntity: User::class)]
    private array $author;
}

Cascade Options:

Option Behavior
CASCADE Delete/update related rows
SET NULL Set FK to NULL
RESTRICT Prevent delete/update if related rows exist
NO ACTION Similar to RESTRICT, checked after constraints

πŸ“Š #[Index]

Creates database indexes for query performance optimization.

Parameter Type Default Description
columns array null Column(s) to index (null = property name)
name string null Custom index name
unique bool false Create unique index

Use Case: Speed up lookups on frequently queried columns.

Examples:

// Single column index (auto-detect column name)
#[Index]
#[Column(type: 'string', length: 150)]
private string $email;

// Single column index (explicit)
#[Index(columns: ['email'])]
#[Column(type: 'string', length: 150)]
private string $email;

// Composite index (multiple columns)
#[Index(columns: ['last_name', 'first_name', 'created_at'])]
private array $name;

// Unique index
#[Index(unique: true)]
#[Column(type: 'string', length: 100)]
private string $slug;

// Named index
#[Index(name: 'idx_user_status', columns: ['user_id', 'status'])]
private array $userStatus;

πŸ” #[UniqueConstraint]

Defines composite unique constraints across multiple columns at the entity level.

Parameter Type Default Description
columns array Required Array of column names
name string null Custom constraint name

Use Case: Enforce business rules like "no duplicate active records per tenant".

Example:

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

#[Entity(table: 'product_variants', resource: 'product-variants')]
#[UniqueConstraint(columns: ['product_id', 'sku'], name: 'unique_product_sku')]
class ProductVariant extends AggregateRoot
{
    #[Id]
    private int $id;

    #[Column(type: 'integer')]
    private int $productId;

    #[Column(type: 'string', length: 100)]
    private string $sku;

    #[Column(type: 'string', length: 50)]
    private string $size;
}

Generated SQL:

CREATE UNIQUE INDEX unique_product_sku ON product_variants (product_id, sku);

πŸ“‹ Complete Entity Example

use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id, PrimaryKey, ForeignKey, Index, UniqueConstraint};
use Infrastructure\FrameworkCore\Attributes\Relations\{BelongsTo, HasMany, ManyToMany};
use Domain\Shared\Entities\AggregateRoot;

#[Entity(table: 'posts', resource: 'posts')]
#[UniqueConstraint(columns: ['author_id', 'slug'], name: 'unique_author_slug')]
class Post extends AggregateRoot
{
    #[PrimaryKey(autoIncrement: true, type: 'bigint')]
    private int $id;

    #[ForeignKey(entity: User::class, onDelete: 'CASCADE')]
    #[BelongsTo(relatedEntity: User::class)]
    private array $author;

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

    #[Index(unique: true)]
    #[Column(type: 'string', length: 100)]
    private string $slug;

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

    #[Column(type: 'boolean', default: false)]
    private bool $isPublished;

    #[Column(type: 'datetime')]
    private string $publishedAt;

    #[Column(type: 'json')]
    private array $metadata;

    #[HasMany(relatedEntity: Comment::class)]
    private array $comments;

    #[ManyToMany(relatedEntity: Category::class)]
    private array $categories;
}

πŸ—οΈ Auto-Generated Schema

Based on the example above, BOUNDLY would generate:

CREATE TABLE posts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    author_id BIGINT UNSIGNED NOT NULL,
    title VARCHAR(255),
    slug VARCHAR(100) UNIQUE,
    content TEXT,
    is_published TINYINT(1) DEFAULT 0,
    published_at DATETIME,
    metadata JSON,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL,
    deleted_at TIMESTAMP NULL,
    
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_posts_title (title)
);

CREATE UNIQUE INDEX unique_author_slug ON posts (author_id, slug);

CREATE TABLE category_post (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,
    category_id BIGINT UNSIGNED NOT NULL,
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);

Next Step: Relations-Definition πŸ”—

Clone this wiki locally