leafs/db · framework agnostic

Your database,
without the ceremony.

Db is a query builder that reads like English. MySQL, Postgres, SQLite and SQL Server through one tiny API; prepared statements everywhere, no ORM classes to configure, in any PHP app.

Try it
app.php

                
SQL
BINDINGS
users
id name email role

Toggle the chips — the query, the SQL and the results stay in sync. Values always travel as bindings.

"I just want to query my data."

Most database layers make you choose: raw PDO boilerplate, or an ORM with models, migrations, and a manual. Db is the third option — a fluent builder small enough to learn in minutes, safe enough to trust with user input, and honest enough to show you the SQL it runs.

Everything you need

Everything you need from your database

Four databases, one API

MySQL, Postgres, SQLite and SQL Server. Same code, different dbtype.

Prepared, always

Every value you pass travels as a binding — never glued into the SQL string.

Multiple connections

Name your connections, switch per-query with use(). Reads, writes, analytics — sorted.

Zero ORM overhead

No models, no migrations, no annotations. One class, one require, done.

The builder

Queries that read like English.

Say what you want, the way you'd say it out loud. Db turns it into parameterised SQL — in, not in and between included.

  • where / orWhere with any comparator
  • orderBy, groupBy, limit, offset
  • first, last, find and search helpers
queries.php
$admins = db()
  ->select('users')
  ->where('role', 'admin')
  ->orderBy('created_at', 'desc')
  ->limit(10)
  ->all();

// or reach for the helpers
$user = db()->select('users')->find(1);
$vips = db()->select('users')
  ->where('plan', 'in', ['pro', 'team'])->all();
signup.php
$saved = db()
  ->insert('users')
  ->params([
    'name' => $name,
    'email' => $email,
  ])
  ->unique('email')
  ->execute();

if (!$saved) {
  // ['email' => 'email already exists']
  return db()->errors();
}

Safe writes

Inserts that guard themselves.

Mark a field unique() and duplicates are rejected before they ever hit your table — with a readable error you can hand straight back to the user. The check runs as a prepared statement, so hostile input stays data.

Relations

Related data, no ORM required.

with() pulls related rows along in both directions — a post's author, or an author's posts — keyed by convention, overridable when your schema disagrees.

relations.php
// a post, with its author attached
$post = db()->select('posts')
  ->where('id', 1)
  ->with('users', 'user_id')
  ->first();

$post['user']['name']; // 'Mika'

// an author, with all their posts
$author = db()->select('users')
  ->where('id', 1)
  ->with('posts')
  ->first();

Grown-up features

Small API. Production habits.

Multiple connections

db()->addConnections([
  'main' => [/* mysql */],
  'analytics' => [/* postgres */],
], 'main');

// per-query switching
db('analytics')->select('events')->all();

Transactions

$ok = db()->transaction(function ($db) {
  $db->insert('orders')->params($order)->execute();
  $db->update('stock')->params(['qty' => $qty])
      ->where('sku', $sku)->execute();
});

// false? everything rolled back. no partial orders.

Bring your stack

It runs in whatever you're building.

Db needs PHP and PDO — that's the whole list. Use the Leaf\Db class directly anywhere; the db() helper lights up inside Leaf.

Leaf
Laravel
Symfony
Slim
WordPress
Plain PHP
+ your stack

Build in a weekend.
Query like you mean it.

$ composer require leafs/db

Then connect and query — or read the full documentation.