Context-bounded entities
Multiple entity classes can map to one physical table, each exposing only the fields and relationships needed for that context.
Most ORMs make the table/entity boundary the modeling boundary: one table, one primary entity class. In modular systems, that turns shared tables into shared domain objects.
A users table may be touched by authentication, administration, billing, public APIs, reporting, and background workers. Those contexts don’t need the same fields, relations, invariants, or lifecycle behavior. A single shared User entity gradually becomes a coupling point between modules.
Articulate makes the bounded context the modeling boundary. Several small entity classes can map to the same physical table: LoginUser for authentication, AdminUser for administration, BillingCustomer for billing, and read-only projection entities for public APIs — while still providing the expected ORM foundations: attributes, repositories, relations, migrations, type mapping, identity map, unit of work, lazy loading, and caching.
Context-bounded entities
Multiple entity classes can map to one physical table, each exposing only the fields and relationships needed for that context.
Per-context Unit of Work
Each EntityManager owns its own IdentityMap and UnitOfWork — no process-wide singletons, no global state.
Explicit per-slice locking
Optimistic locking guards only the columns a slice actually owns, so a cosmetic write in one context can’t block a critical one in another.
Dual-database, first class
MySQL 8.0 and PostgreSQL 15 are both primary targets — schema diffing, migrations, and type mapping understand both.
Attribute-driven metadata
No XML, no YAML, no annotations. All mapping lives in PHP 8 attributes: #[Entity], #[Property], #[OneToMany], and more.
Deliberate shared-table writes
Cross-entity remove propagation, cache eviction, and version guards all reason correctly about sibling entities sharing a row.
Install the package:
composer require articulate-orm/coreMap an entity and write your first flush:
use Articulate\Connection;use Articulate\Modules\EntityManager\EntityManager;
#[Entity]class User{ #[PrimaryKey] public ?int $id = null;
#[Property] public string $name;
#[Property] public string $email;}
$connection = new Connection('mysql:host=127.0.0.1;dbname=myapp', 'user', 'password');$em = new EntityManager($connection);
$user = new User();$user->name = 'Jane';$user->email = 'jane@example.com';$em->persist($user);$em->flush();
$user = $em->getRepository(User::class)->find($user->id);Next: Getting Started
Install the library, wire the services, and run your first entity through a full persist/find cycle.
Next: Context-Bounded Entities
The core idea behind Articulate, explained with a before/after comparison.