Skip to content
This content is for v1.0. Switch to the latest version for up-to-date documentation.

The ORM for modular PHP applications — several narrow entity classes, one physical table, no shared-model coupling.
PHP 8.4+MySQL & PostgreSQLApache 2.0

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:

Terminal window
composer require articulate-orm/core

Map 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.

Read the guide →

Next: Context-Bounded Entities

The core idea behind Articulate, explained with a before/after comparison.

Read the concept →