slendium / ocd
Database- and framework agnostic ORM.
Requires
- php: ^8.5
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^13.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Database- and framework agnostic database abstraction layer / ORM for PHP.
- Supports MongoDB and SQL-based databases, can be extended further
- Supports custom / derived types as long as they go back to the same primitives
- Entities are based on plain PHP classes, adding
#[Attribute]'s only where more specificity is needed - Does not force the active record pattern, inheritance, or a rich domain model (nor exclude them)
- Developed without LLM's
Installation
Requires PHP >= 8.5.
Run composer require slendium/ocd to add it to your project.
However, most likely you are looking for a database-specific implementation of the library:
For implementors there is also the OCD conformance package, which contains implementation guides and PHPUnit tests.
Examples
Data definition
Basic entity
A basic entity only requires a plain PHP class declaration.
final readonly class Product implements Entity, Entity\Identifiable { public function __construct( #[Override] public Entity\Id $id, public string $name, public float $price, public bool $visible = false, public int $stock = 0, ) { } }
Relationships and indices
TODO
Querying
Data can be queried using a set of generalized expressions. You can manually construct them, use a predefined builder utility or create your own builder. An example using the built-in "document shape" query builder:
use Slendium\Ocd\Predicate\DocumentShape as Q; $filter = Q::shape([ 'year' => 2026, // match a literal int 'views' => Q::gte(1000), 'title' => Q::regex('^'), 'tags' => Q::containsSome([ 'news', 'updates' ]) ]); $cursor = $collection->openCursor() |> Cursor::filter(?, $filter) |> Cursor::skip(?, $page * PAGE_SIZE) |> Cursor::limit(?, PAGE_SIZE); foreach ($cursor as $doc) { } // do something with each document
Manipulating data
Inserting data
Data can be inserted by passing "documents" to an insert command.
A document is any object that is ArrayAccess&Countable&Traversable with non-empty strings for keys.
See the documentation of the Collection::startInsert() function to learn more about what types of
values can be contained in inserted documents.
An example:
$document = new MutableDocument([ 'name' => 'foo', 'createdAt' => new DateTime, 'location' => [ $latitude, $longitude ], 'owner' => $userEntity, ]); $collection->startInsert([ $document ]) |> InsertCommand::fireAndForget(?);
Updating data
Data can be updated by providing a query and a list of update statements. Analogous to the data querying example, update statement lists can be constructed manually or through builder utilities. An example:
use Slendium\Ocd\Predicate\DocumentShape as Q; use Slendium\Ocd\Update\DocumentUpdate as U; $filter = Q::shape([ 'id' => $updateId ]); $updates = U::create([ 'name' => $newName, 'modificationCount' => U::add(1), 'auditLog' => U::append("At $modifiedAt, user {$user->name} changed name to `$newName`"), 'details' => U::path([ 'neverModifiedFlag', U::unset() ]) ]); $collection->startUpdate($filter, $updates) ->execute();
Deleting data
Data can be deleted by creating a delete command and executing it. An example:
$filter = Q::shape([ 'id' => $id ]); $collection->startDelete($filter) |> DeleteCommand::enforceLimit(?, 1) |> DeleteCommand::execute(?);
Roadmap
- [Done] Describing the schema
- [Done] Data manipulation (create, update, delete)
- Data querying
- [Done] Reading from a cursor
- Entity reassembly
- Relationships and indices
- Geospatial data
- Data aggregation
- Transactions
Out of scope
Tracking entity changes, persisting entities, and the active record pattern.