A standalone ORM for modern PHP applications, with optional AssegaiPHP integration.
An object-relational mapper for modern PHP applications. You can use it on its own, or plug it into AssegaiPHP when you want repository injection and framework conventions.
For commit and pull request conventions in this repo, see:
$ assegai add ormThat is the preferred path inside an Assegai workspace. It will:
- require
assegaiphp/ormif it is missing - import
OrmModuleinto the root module - make the ORM CLI commands available through package discovery
If you install the package manually first, assegai add orm is still safe to run afterward. It will just finish the
workspace wiring.
For standalone PHP projects that are not using Assegai, install the package directly:
$ composer require assegaiphp/ormThen enable only the PDO driver you actually plan to use:
pdo_mysqlfor MySQL or MariaDBpdo_pgsqlfor PostgreSQLpdo_sqlitefor SQLite
AssegaiORM no longer forces all three database extensions at install time. If you choose a driver without enabling its matching PDO extension, the ORM will tell you exactly which extension is missing when you try to connect.
This package is designed to feel familiar to teams coming from TypeORM:
- entities describe persistence shape
- repositories can be used directly or injected into services in Assegai
- data sources decide where a feature reads and writes
- relations are explicit and ownership matters
- migrations evolve the schema deliberately
In the main Assegai guide set, the ORM track is:
core/docs/data-and-orm.mdcore/docs/orm-setup-and-data-sources.mdcore/docs/orm-entities-repositories-and-results.mdcore/docs/orm-relations.mdcore/docs/orm-migrations-and-database-workflows.md
You can use AssegaiORM directly in any PHP project. The standalone path is:
- configure named databases for the ORM runtime
- create a
DataSource - create or fetch repositories from that data source
<?php
use App\Entities\NoteEntity;
use Assegai\Orm\DataSource\DataSource;
use Assegai\Orm\DataSource\DataSourceOptions;
use Assegai\Orm\Enumerations\DataSourceType;
use Assegai\Orm\Support\OrmRuntime;
OrmRuntime::configure([
'databases' => [
'sqlite' => [
'app' => [
'path' => __DIR__ . '/storage/app.sqlite',
],
],
],
]);
$dataSource = new DataSource(new DataSourceOptions(
name: 'app',
type: DataSourceType::SQLITE,
database: 'app',
));
$notes = $dataSource->getRepository(NoteEntity::class);
$note = $notes->create((object)[
'title' => 'First note',
'body' => 'Stored without a framework',
]);
$created = $notes->save($note);
$allNotes = $notes->find()->getData();Reads retain their existing result shape by default. Pass hydrate: true when application code needs deterministic entity instances and entity property types:
$notes = $dataSource->getRepository(NoteEntity::class);
$note = $notes->findOne([
'where' => ['id' => 1],
'relations' => ['author'],
'hydrate' => true,
])->getData();The root row is a NoteEntity, and each explicitly requested relation is an instance of its declared target entity class. Column aliases are mapped to their entity property names, while backed enums, DateTime, and booleans use the ORM's normal conversion pipeline. Exclusion rules still apply after hydration, including sensitive-property defaults and explicit exclude overrides.
SQLite is a good fit for local development, small apps, prototypes, and CLI tools. This ORM supports SQLite through PDO, so the first step is to register a named SQLite connection in your app config.
Make sure the pdo_sqlite extension is enabled and that the folder for your database file already exists. The
configured path should be relative to your project's working directory.
<?php
return [
'databases' => [
'sqlite' => [
'app' => [
'path' => 'storage/database/app.sqlite',
],
],
],
];You can then point an entity at that SQLite data source:
<?php
namespace App\Entities;
use Assegai\Orm\Attributes\Columns\Column;
use Assegai\Orm\Attributes\Columns\PrimaryGeneratedColumn;
use Assegai\Orm\Attributes\Entity;
use Assegai\Orm\Enumerations\DataSourceType;
use Assegai\Orm\Queries\Sql\ColumnType;
#[Entity(
table: 'notes',
dataSource: 'app',
driver: DataSourceType::SQLITE,
)]
class NoteEntity
{
#[PrimaryGeneratedColumn]
public ?int $id = null;
#[Column(type: ColumnType::VARCHAR, nullable: false)]
public string $title = '';
#[Column(type: ColumnType::TEXT, nullable: true)]
public ?string $body = null;
}When you need SQL-only storage knobs such as engine, schema, or SQLite WITHOUT ROWID, keep the entity metadata focused on shared concerns and add the SQL companion attribute:
<?php
namespace App\Entities;
use Assegai\Orm\Attributes\Entity;
use Assegai\Orm\Attributes\SqlEntityOptions;
use Assegai\Orm\Enumerations\DataSourceType;
#[Entity(
table: 'audit_logs',
dataSource: 'reporting',
driver: DataSourceType::POSTGRESQL,
)]
#[SqlEntityOptions(schema: 'analytics')]
class AuditLogEntity
{
}Legacy #[Entity(engine: ...)], #[Entity(schema: ...)], and #[Entity(withRowId: ...)] declarations still work, but new code should put SQL-specific storage options on #[SqlEntityOptions(...)].
Relations follow the same ownership ideas you would expect from TypeORM:
OneToOne: the owner side has#[JoinColumn(...)]ManyToOneandOneToMany: the foreign key lives on theManyToOnesideManyToMany: the owner side has#[JoinTable(...)]
Load relations explicitly in find() and findOne() calls, and prefer writing through the owner side of the relation. OneToMany collections do not need a reference-column argument; the ORM derives that from the owning ManyToOne/JoinColumn metadata.
If you want to use SQLite directly through the ORM, create a DataSource, ensure the table exists, and then work with
the repository:
<?php
use App\Entities\NoteEntity;
use Assegai\Orm\DataSource\DataSource;
use Assegai\Orm\DataSource\DataSourceOptions;
use Assegai\Orm\Enumerations\DataSourceType;
$dataSource = new DataSource(new DataSourceOptions(
entities: [],
name: 'app',
type: DataSourceType::SQLITE,
));
$dataSource->manager->query(<<<SQL
CREATE TABLE IF NOT EXISTS `notes` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`title` TEXT NOT NULL,
`body` TEXT
)
SQL);
$notes = $dataSource->getRepository(NoteEntity::class);
$newNote = $notes->create([
'title' => 'First note',
'body' => 'Stored in SQLite',
]);
$notes->insert($newNote);
$allNotes = $notes->find()->getData();
$firstNote = $notes->findOne(['id' => 1])->getFirst();Inside Assegai, import OrmModule once or let assegai add orm wire it for you. That module registers the repository
resolver so #[InjectRepository(...)] can participate in the framework injector cleanly.
Once the module is present, you can inject the repository and let the entity metadata select the SQLite connection:
<?php
namespace App\Notes;
use App\Entities\NoteEntity;
use Assegai\Core\Attributes\Injectable;
use Assegai\Orm\Attributes\InjectRepository;
use Assegai\Orm\Management\Repository;
#[Injectable]
class NotesService
{
public function __construct(
#[InjectRepository(NoteEntity::class)]
private readonly Repository $notes,
) {
}
public function all(): array
{
return $this->notes->find()->getData();
}
}The ORM no longer needs assegaiphp/core to function. When the core package is present, the ORM can still read
framework config and repository metadata automatically. When it is not present, Assegai\\Orm\\Support\\OrmRuntime
acts as the lightweight runtime seam for config, module options, and logging.
Assegai is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
- Author - Andrew Masiye
- Website - https://assegaiphp.com
- Twitter - @assegaiphp
Assegai is MIT licensed.
