A lightweight, framework-agnostic database layer for PHP.
Part of the Codemonster ecosystem — but works fully standalone.
composer require codemonster-ru/databaseuse Codemonster\Database\DatabaseManager;
$manager = new DatabaseManager([
'default' => 'mysql', // name of the default connection
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
],
]);
$db = $manager->connection(); // default connectionYou can define multiple connections and select them by name:
$manager = new DatabaseManager([
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'app',
'username' => 'root',
'password' => '',
],
'sqlite' => [
'driver' => 'sqlite',
'database' => __DIR__ . '/database.sqlite',
],
],
]);
$mysql = $manager->connection(); // default (mysql)
$sqlite = $manager->connection('sqlite'); // explicit connection- For MySQL/MariaDB use
driver => 'mysql'. - For SQLite use
driver => 'sqlite'and onlydatabaseis required (file path or:memory:). - Other PDO drivers can be wired via
driver+ DSN-compatible options; the query layer is driver-agnostic, while the schema builder is primarily tuned for MySQL-like syntax and SQLite.
$users = $db->table('users')
->select('id', 'name', 'email')
->where('active', 1)
->orderBy('created_at', 'desc')
->limit(10)
->get();$rows = $db->table('users')
->select('users.name label', 'COUNT(*) total')
->groupBy('users.name')
->get();$db->table('users')->insert([
'name' => 'Vasya',
'email' => 'test@example.com',
]);
$id = $db->table('ideas')->insertGetId([
'title' => 'New idea',
]);$db->table('users')
->where('id', 5)
->update([
'active' => 0,
'updated_at' => date('Y-m-d H:i:s'),
]);$db->table('sessions')
->where('user_id', 10)
->delete();[$sql, $bindings] = $db->table('users')
->where('active', 1)
->toSql();
// $sql = 'SELECT * FROM `users` WHERE `active` = ?'
// $bindings = [1]$db->table('users')
->selectRaw('COUNT(*) as total')
->whereRaw('JSON_VALID(metadata)')
->orderByRaw('FIELD(status, "new", "approved", "archived")')
->get();$db->table('orders')
->join('users', 'users.id', '=', 'orders.user_id')
->leftJoin('payments', function ($join) {
$join->on('payments.order_id', '=', 'orders.id')
->where('payments.status', 'paid');
})
->get();$db->table('orders')
->selectRaw('status, COUNT(*) as total')
->groupBy('status')
->having('total', '>', 10)
->get();$count = $db->table('users')->count();
$sum = $db->table('orders')->sum('amount');
$avg = $db->table('ratings')->avg('score');
$min = $db->table('logs')->min('id');
$max = $db->table('visits')->max('duration');$exists = $db->table('users')
->where('email', 'test@example.com')
->exists();$email = $db->table('users')
->where('id', 1)
->value('email');
$names = $db->table('users')->pluck('name');
$pairs = $db->table('users')->pluck('email', 'id'); // [id => email]
// Aliases are supported:
// $db->table('users')->pluck('users.name label', 'users.id key');
// $db->table('users')->value('COUNT(*) total');$pairs = $db->table('users')
->pluck('users.name label', 'users.id key'); // [id => name]
$total = $db->table('users')->value('COUNT(*) total');$currentPage = 1;
$page = $db->table('posts')->simplePaginate(20, $currentPage);
// $page = [
// 'data' => [...],
// 'per_page' => 20,
// 'current_page'=> 1,
// 'next_page' => 2,
// 'prev_page' => null,
// ];$db->table('users')
->setEmptyWhereInBehavior(\Codemonster\Database\Query\QueryBuilder::EMPTY_CONDITION_EXCEPTION)
->whereIn('id', []);Available behaviors:
EMPTY_CONDITION_NONE(default forwhereIn) -> executes0 = 1EMPTY_CONDITION_ALL(default forwhereNotIn) -> executes1 = 1EMPTY_CONDITION_EXCEPTION-> throwsInvalidArgumentException
You can override whereNotIn separately:
$db->table('users')
->setEmptyWhereNotInBehavior(\Codemonster\Database\Query\QueryBuilder::EMPTY_CONDITION_NONE)
->whereNotIn('id', []);$db->transaction(function ($db) {
$db->table('users')->insert([
'name' => 'New user',
'email' => 'user@example.com',
]);
$db->table('logs')->insert([
'message' => 'User created',
]);
});If you also install codemonster-ru/support
and register bindings in your container, you can use global helpers:
db(); // returns default ConnectionInterface
db('sqlite'); // specific connection
schema(); // schema builder for default connection
transaction(fn() => // convenience wrapper
db()->table('logs')->insert(['message' => 'ok'])
);Helpers are thin wrappers around DatabaseManager and the connection’s schema() / transaction() methods.
The package includes a lightweight schema builder.
Note: The schema grammar is focused on MySQL/MariaDB and SQLite.
For other PDO drivers, the query builder will work, but schema operations may not be fully supported.
use Codemonster\Database\Schema\Blueprint;
// You can also use Schema::forConnection($db) if you need a schema instance directly.
$db->schema()->create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->boolean('active')->default(1);
$table->timestamps();
});$db->schema()->table('users', function (Blueprint $table) {
$table->string('avatar')->nullable();
$table->integer('age')->default(0);
});- SQLite supports
ALTER TABLEonly for a subset of operations; some drop operations are ignored. - Foreign keys are emitted inline during
CREATE TABLE.
$db->schema()->drop('users');
// or:
$db->schema()->dropIfExists('users');- Integers:
id,integer,bigInteger,mediumInteger,smallInteger,tinyInteger - Floats:
decimal,double,float - Text:
string,char,text,mediumText,longText - Boolean:
boolean - JSON:
json - Dates & time:
date,datetime,timestamp,time,year - UUID:
uuid - Indexes:
index,unique,primary - Foreign keys with
foreign()/references()/on()andonDelete()/onUpdate()helpers
The package includes a migration system (designed to be used via the CLI).
migratemigrate:rollbackmigrate:statusmake:migrationseedmake:seed
use Codemonster\Database\Migrations\Migration;
use Codemonster\Database\Schema\Blueprint;
return new class extends Migration {
public function up(): void
{
schema()->create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
});
}
public function down(): void
{
schema()->drop('posts');
}
};The package includes a lightweight seeding system (via the CLI).
use Codemonster\Database\Seeders\Seeder;
return new class extends Seeder {
public function run(): void
{
db()->table('users')->insert([
'name' => 'Admin',
'email' => 'admin@example.com',
]);
}
};Since 1.3.0, the package includes a complete ORM layer:
ModelModelQueryModelCollection- Lazy & eager loading
$fillable,$guarded,$hidden- Attribute casting (
int,json,datetime, etc.) created_at/updated_atSoftDeletes
use Codemonster\Database\ORM\Model;
class User extends Model
{
protected string $table = 'users';
protected array $fillable = ['name', 'email', 'password'];
protected array $hidden = ['password'];
protected array $casts = [
'created_at' => 'datetime',
];
}$user = User::find(1);
$active = User::query()
->where('active', 1)
->orderBy('id')
->get();User::create([
'name' => 'John',
'email' => 'john@example.com',
]);
$user->email = 'new@example.com';
$user->save();
$user->delete();Available relations:
HasOneHasManyBelongsToBelongsToMany
class User extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
class Post extends Model {
public function author() {
return $this->belongsTo(User::class, 'user_id');
}
}$user->posts;$user->load('posts');use Codemonster\Database\Traits\SoftDeletes;
class User extends Model {
use SoftDeletes;
}$user->delete()→ setsdeleted_at$user->restore()User::onlyTrashed()User::withTrashed()
A standalone CLI ships with the package:
vendor/bin/databasevendor/bin/database migratevendor/bin/database migrate:rollbackvendor/bin/database migrate:statusvendor/bin/database db:wipeForce wipe without confirmation:
vendor/bin/database db:wipe --forcevendor/bin/database db:truncateForce clean without confirmation:
vendor/bin/database db:truncate --forcevendor/bin/database make:migration CreatePostsTableMigration names must be CamelCase using only Latin letters (e.g., CreateUsersTable). Names that include other symbols or casing styles are rejected.
Default migrations directory:
./database/migrations
You can override paths via the migration kernel/path resolver:
$kernel->getPathResolver()->addPath('/path/to/migrations');vendor/bin/database seedvendor/bin/database make:seed UsersSeederSeed names must be CamelCase using only Latin letters (e.g., UsersSeeder). Names that include other symbols or casing styles are rejected.
Default seeds directory:
./database/seeds
You can override paths via the seed kernel/path resolver:
$kernel->getSeedPathResolver()->addPath('/path/to/seeds');composer test