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-entityThe 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 stringableInternally, 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 EntityMetadataInject 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 helperYou 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