Sommy ORM is a tiny, Sequelize‑style ORM for PHP built on PDO. It focuses on being simple and explicit: you define models with a schema, create tables via a query interface, and do straightforward CRUD without magic.
Works with MySQL, MariaDB, SQLite, and PostgreSQL.
- PHP >= 8.0
ext-pdoand the PDO driver for your database (e.g.,pdo_mysql,pdo_sqlite,pdo_pgsql)
Install via Composer (recommended):
composer require clintonnzedimma/sommy-ormOr, if you are using this repository directly:
- Install dependencies and dump autoload
composer install
composer dump-autoload- Require Composer autoloader from your app:
require __DIR__ . '/vendor/autoload.php';Composer autoload is configured for Sommy\ORM\ → src/.
Create an array of connection settings and pass it to Sommy\ORM\SommyManager.
SQLite example:
use Sommy\ORM\SommyManager;
$sommy = new SommyManager([
'dialect' => 'sqlite',
'database' => __DIR__ . '/database.sqlite',
]);MySQL/MariaDB example:
$sommy = new SommyManager([
'dialect' => 'mysql', // or 'mariadb'
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'testdb',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
]);- Make sure the
pdo_mysqlextension is enabled (check withphp -m). - Ensure a MySQL server is running and you have database credentials.
- Create a database and user (example using the MySQL client):
mysql -u root -pCREATE DATABASE testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'secret';
GRANT ALL PRIVILEGES ON testdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;- Configure Sommy to use MySQL (see example above), e.g.:
use Sommy\ORM\SommyManager;
$sommy = new SommyManager([
'dialect' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'testdb',
'username' => 'appuser',
'password' => 'secret',
'charset' => 'utf8mb4',
]);
// Optional: quick connectivity check
if (!$sommy->authenticate()) {
throw new RuntimeException('MySQL connection failed');
}PostgreSQL example:
$sommy = new SommyManager([
'dialect' => 'pgsql',
'host' => '127.0.0.1',
'port' => 5432,
'database' => 'testdb',
'username' => 'postgres',
'password' => 'secret',
]);Use the factory in Sommy\ORM\DataTypes to declare columns:
- INTEGER:
DataTypes::INTEGER([...options]) - BIGINT, SMALLINT:
DataTypes::BIGINT(),DataTypes::SMALLINT() - STRING/VARCHAR:
DataTypes::STRING($length = 255, [...]) - TEXT:
DataTypes::TEXT([...]) - BOOLEAN:
DataTypes::BOOLEAN([...]) - DATE, DATETIME, TIME, TIMESTAMP
- FLOAT, DECIMAL($precision = 10, $scale = 0)
- JSON (falls back to TEXT where unsupported)
- UUID (native on PostgreSQL,
CHAR(36)elsewhere)
Common column options:
allowNull(bool): allow NULL values (default: true)default(mixed): default valueunique(bool): unique constraint (column-level)primaryKey(bool): marks column as primary keyautoIncrement(bool): auto-increment (dialect‑aware)
Use QueryInterface to create tables. Example users table:
use Sommy\ORM\DataTypes;
$qi = $sommy->getQueryInterface();
$qi->createTable('users', [
'id' => DataTypes::INTEGER(['primaryKey' => true, 'autoIncrement' => true, 'allowNull' => false]),
'name' => DataTypes::STRING(255, ['allowNull' => false]),
'age' => DataTypes::INTEGER(),
'is_admin' => DataTypes::BOOLEAN(['default' => false]),
'created_at' => DataTypes::DATETIME(['allowNull' => false]),
], ['ifNotExists' => true]);Drop a table:
$qi->dropTable('users', ['ifExists' => true]);Define a model with SommyManager::define($name, $attributes, $options = []). This returns an instance of an anonymous subclass of Model that is bound to your table and schema.
use Sommy\ORM\Model;
use Sommy\ORM\DataTypes;
$User = $sommy->define('User', [
'id' => DataTypes::INTEGER(['primaryKey' => true, 'autoIncrement' => true, 'allowNull' => false]),
'name' => DataTypes::STRING(255, ['allowNull' => false]),
'age' => DataTypes::INTEGER(),
], [
'tableName' => 'users', // optional; defaults to strtolower('User')
]);Important: Many Model methods are static (Sequelize‑like). You can:
- Call via the class name string of the returned instance:
$UserClass = $User::class; // get anonymous class name
$id = $UserClass::create(['name' => 'Alice', 'age' => 30]);- Or create an instance and use instance helpers like
save():
$u = new $UserClass(['name' => 'Bob', 'age' => 25]);
$u->save(); // INSERT, sets primary key if availableFor convenience, calling static methods on the instance (e.g., $User->findAll()) also works in practice, but using the class name string as shown above is clearer.
Create (static):
$UserClass = $User::class;
$id = $UserClass::create(['name' => 'Alice', 'age' => 30]);Create + save (instance):
$u = new $UserClass(['name' => 'Bob']);
$u->age = 25;
$u->save(); // INSERTFind:
$all = $UserClass::findAll();
$admins = $UserClass::findAll(['is_admin' => 1], ['order' => ['id' => 'DESC']]);
$first = $UserClass::findOne(['id' => 1]);Update:
// Bulk update
$affected = $UserClass::update(['age' => 31], ['name' => 'Alice']);
// Instance update via save()
$alice = $UserClass::findOne(['name' => 'Alice']);
if ($alice) {
$alice->age = 32;
$alice->save();
}Delete:
// Bulk delete
$deleted = $UserClass::destroy(['id' => [3,4,5]]); // IN (...) support
// Instance delete
$u = $UserClass::findOne(['id' => 2]);
if ($u) { $u->delete(); }Available on $sommy->getQueryInterface().
- select:
select(string $table, array $columns = ['*'], array $where = [], array $options = []) : array- Where supports:
['col' => value, 'status' => ['a','b'], 'deleted_at' => null] - Options:
order(string or array),limit(int),offset(int)
- Where supports:
- insert:
insert(string $table, array $data) : string|bool(returns last insert id if available) - update:
update(string $table, array $data, array $where) : int - delete:
delete(string $table, array $where) : int - createTable:
createTable(string $name, array $attributes, array $options = []) : bool - dropTable:
dropTable(string $name, array $options = []) : bool
Identifier quoting and SQL types are dialect‑aware where it matters (e.g., MySQL backticks vs. PostgreSQL double quotes, SERIAL/BIGSERIAL, SQLite INTEGER PRIMARY KEY).
Sommy includes a tiny CLI in bin/sommy for simple time‑stamped migrations.
- Initialize config (creates
sommy.config.php):
php bin/sommy init:config- Create a migration file:
php bin/sommy migrate:create create_usersThis generates database/migrations/YYYMMDDHHMMSS_create_users.php with a class like Migration_YYYMMDDHHMMSS_create_users containing up() and down().
- Edit your migration, e.g.:
use Sommy\ORM\QueryInterface;
class Migration_20250101010101_create_users
{
public function up(QueryInterface $qi): void
{
$qi->createTable('users', [
'id' => ['type' => 'INTEGER', 'primaryKey' => true, 'autoIncrement' => true, 'allowNull' => false],
'name' => ['type' => 'VARCHAR', 'length' => 255, 'allowNull' => false],
], ['ifNotExists' => true]);
}
public function down(QueryInterface $qi): void
{
$qi->dropTable('users', ['ifExists' => true]);
}
}- Apply pending migrations:
php bin/sommy migrate:up- Revert last migration:
php bin/sommy migrate:downThe CLI records applied migrations in a table sommy_migrations.
require __DIR__ . '/vendor/autoload.php';
use Sommy\ORM\SommyManager;
use Sommy\ORM\DataTypes;
$sommy = new SommyManager([
'dialect' => 'sqlite',
'database' => __DIR__ . '/database.sqlite',
]);
// Create a table (usually via migration)
$qi = $sommy->getQueryInterface();
$qi->createTable('users', [
'id' => DataTypes::INTEGER(['primaryKey' => true, 'autoIncrement' => true, 'allowNull' => false]),
'name' => DataTypes::STRING(255, ['allowNull' => false]),
], ['ifNotExists' => true]);
// Define a model
$User = $sommy->define('User', [
'id' => DataTypes::INTEGER(['primaryKey' => true, 'autoIncrement' => true, 'allowNull' => false]),
'name' => DataTypes::STRING(255, ['allowNull' => false]),
], ['tableName' => 'users']);
// Use the class name for static methods
$UserClass = $User::class;
$id = $UserClass::create(['name' => 'Alice']);
$all = $UserClass::findAll();
foreach ($all as $u) {
echo $u->name . "\n";
}- Minimal abstraction: generated SQL is straightforward and debug‑friendly.
- Dialect‑aware DDL: common types and primary/auto‑increment behaviors map correctly for MySQL/MariaDB, PostgreSQL, SQLite.
- Anonymous model classes:
define()returns an instance; use$Model::classto access static methods cleanly.
- Ensure the PDO driver for your DB is installed and enabled.
- For MySQL/MariaDB, set a valid
charset(defaults toutf8mb4). - If
migrate:upfails, check your migration for typos in column definitions (e.g.,allowNull,primaryKey). - If a migration class fails to load or you see a PHP parse error, ensure method signatures include the parameter variable:
use
public function up(Sommy\\ORM\\QueryInterface $qi): voidandpublic function down(Sommy\\ORM\\QueryInterface $qi): void. - On MySQL/MariaDB, DDL statements (CREATE/DROP TABLE) auto-commit and may end a transaction. The CLI now guards commit/rollback, but if you saw "There is no active transaction", update to the latest version and re-run.
Generate a model class (Laravel-style) into database/models:
php bin/sommy make:model UserOptions:
--table=users: set table name (default: lowercase name + 's')-mor--migration: also generate a migration stub (e.g.,create_users)--force: overwrite the file if it exists
Autoload for dev:
composer.jsonincludes anautoload-devmappingDatabase\Models\→database/models/.- Run
composer dump-autoloadafter generating models so Composer picks them up.
Using a generated model:
use Sommy\ORM\SommyManager;
use Database\Models\User;
$sommy = new SommyManager([...]);
User::register($sommy); // binds table + attributes defined in the model
// After defining attributes in User::register():
$id = User::create(['name' => 'Alice']);