Docs

Entity

The Entity component is an attribute-first, reflection-powered data-mapper that sits halfway between a lightweight Active Record and a full ORM. You annotate plain PHP classes; the runtime scanner builds an in-memory map that repositories, SQL generators, and validation layers can consume.

1 · Installation

composer require monkeyscloud/monkeyslegion-entity

The package brings in only Core + DI and the native PDO extension—no heavy ORM dependencies.

2 · Declaring an Entity

namespace App\Domain\Post;

use MonkeysLegion\Entity\Attributes\{Entity, Id, Field, OneToMany};
use Ramsey\Uuid\UuidInterface;

#[Entity(table: 'posts')]
final class Post
{
    #[Id(strategy: 'uuid')]
    public readonly UuidInterface $id;

    #[Field(type: 'string', length: 160)]
    public string $title;

    #[Field(type: 'text')]
    public string $body;

    #[Field(type: 'datetime', default: 'now')]
    public \DateTimeImmutable $publishedAt;

    #[OneToMany(target: Comment::class, mappedBy: 'post')]
    public iterable $comments = [];
}

Key attribute options

Attribute

Required

Options (examples)

Notes

Entity

table

schema, repository

Sets DB table & optional custom repository class

Id

strategy: 'uuid' | 'auto'

If omitted, the first #[Field(primary: true)] wins

Field

type

length, nullable, default, unique

Common SQL-ish column flags

OneToOne, OneToMany, ManyToOne, ManyToMany

   

3 · UUID helper

For UUID primary keys, import the built-in value object:

use MonkeysLegion\Entity\Support\Uuid;

$post->id = Uuid::v4();          // returns a Ramsey-style uuid stringable

Internally, it wraps ramsey/uuid but keeps the dependency optional. If that lib isn’t installed, it falls back to PHP 8.4’s random_bytes().

4 · EntityScanner

EntityScanner walks one or more directories, reflects on every class, and assembles a MetadataCollection that powers:

  • Schema generation – turn metadata into CREATE TABLE / migrations

  • Repositories – type-safe find/save helpers

  • Validation – length, nullability & uniqueness checks before hitting SQL

use MonkeysLegion\Entity\EntityScanner;
use MonkeysLegion\DI\Container;

$scanner   = new EntityScanner(
    directories: [base_path('src/Domain')],
    container:   $container   // optional – for repo factories
);

$metadata  = $scanner->scan();   // returns iterable of EntityMetadata

Inject the resulting collection wherever you need entity introspection.

5 · Repositories in 30 seconds

use MonkeysLegion\Entity\Repository\EntityRepository;

final class PostRepository extends EntityRepository
{
    protected string $entity = Post::class;
}

/** @var PostRepository $posts */
$posts = $container->get(PostRepository::class);

$post  = $posts->find($someUuid);
$post->title = 'New title';
$posts->save($post);

EntityRepository exposes find(), findAll(), save(), and remove() out of the box; extend it or plug in your own SQL/DBAL layer for complex queries.

6 · Lifecycle Hooks (optional)

Add any of these methods to your entity; the scanner calls them when present:

  • public function beforeSave() : void

  • public function afterSave()  : void

  • public function beforeDelete(): void

  • public function afterDelete() : void

Great for slugs, denormalised counters, or audit logs.

7 · Putting it all together

$container = (new ContainerBuilder())
    ->addDefinitions([
        PDO::class => new PDO('sqlite:' . base_path('database.sqlite')),
    ])
    ->build();

$scanner   = new EntityScanner([base_path('src/Domain')], $container);
$metadata  = $scanner->scan();

$schemaTool->sync($metadata);        // CLI migration helper

You now have attribute-mapped entities, UUID IDs, relations, and an auto-discovering scanner wired into the DI container—ready for repositories, migrations, or services.

License

MIT © 2025 MonkeysCloud