denisyu-1/articulate
Articulate is a context-bounded ORM for modular PHP apps sharing database tables. Map multiple small entity classes to one table, with repositories, relations, migrations, identity map, unit of work, lazy loading, caching, and MySQL/PostgreSQL support.
EntityManager identity maps and units of work give an explicit memory-safety story for long-running queue workers, which is usually tracked as a separate engineering initiative.ENGINE/CHARSET/COLLATE on CREATE TABLE converts a hidden per-table drift problem into an explicit platform decision (set once at database/server level). Good — but it becomes a checklist item for ops, not an assumption.MorphToMany/MorphedByMany) supports choosing a single taggables pivot over N per-type join tables — a decision that's expensive to reverse later.#[Index(fields: [...])] takes PHP property names, so renames are caught by tooling. Supports a "indexes are part of the entity model" standard; the trade-off is that expression and prefix indexes are explicitly deferred — a real constraint for search-heavy workloads.Consider it when:
Validate in a spike before committing: metadata merge/conflict behavior against a real, non-empty schema; sibling-entity write semantics and cache eviction; lazy-loading behavior inside long-running workers; migration generation round-tripping on a schema clone; locking and transaction behavior under the app's actual concurrency profile.
Look elsewhere when:
Vendor risk, stated plainly: 3 stars, 0 dependents, effectively one maintainer, a 2.0.0 that already followed a breaking config change in 1.1.0. Apache-2.0 makes an internal fork viable, but "we can fork it" is a staffing commitment, not a risk mitigation. Mitigations: pin versions, forbid transitive reliance on undocumented behavior, and name an owner before the pilot starts.
For executives. We have shared tables that six teams read and write through one giant model. Every change to that model is a cross-team release risk, and every incident in it has an unclear owner. This package lets each module own only its slice of the same table — smaller blast radius, clearer ownership, no database split, no multi-quarter re-platforming. The software is free and permissively licensed; the real costs are a bounded pilot (one module, roughly two to four weeks) and a dependency we must own. The real risk is maturity: it's a young project with essentially one maintainer and no adoption footprint. That's exactly why the ask is a pilot with written success criteria and an exit path — fork or revert — not an org-wide mandate.
For engineering. It's a real ORM, not a wrapper: attributes, repositories, relations, migrations, type mapping, identity map, unit of work, lazy loading, three independent PSR-6 cache layers, and persistent connections. The differentiator is that the bounded context — not the table — is the modeling boundary, so LoginUser, AdminUser, and BillingCustomer can coexist over users with different fields, relations, and invariants. Read-only entities make "this context must not write" a ReadOnlyEntityException at persist()/remove() rather than a review comment. Per-EntityManager units of work give us a defensible answer to worker memory growth. What it does not give us: ecosystem breadth, expression indexes, MySQL table options, a compatibility matrix, or a support contract. Proposal: pilot behind the reporting or billing context. Success criteria: zero regressions on shared-entity behavior, measured query counts and L2 hit rate versus baseline, migrations round-tripping on a schema clone, and a signed-off fallback plan. If the pilot fails those, we keep the current ORM and we've spent two weeks learning something concrete about our own coupling.
How can I help you explore Laravel packages today?