PHPackages                             joby/smol-query - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. joby/smol-query

ActiveLibrary[Database &amp; ORM](/categories/database)

joby/smol-query
===============

A lightweight and simple SQLite query builder.

v1.2.0(1mo ago)0411MITPHPPHP &gt;=8.1CI passing

Since Feb 20Pushed 2mo agoCompare

[ Source](https://github.com/joby-lol/smol-query)[ Packagist](https://packagist.org/packages/joby/smol-query)[ RSS](/packages/joby-smol-query/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (6)Versions (4)Used By (1)

smolQuery
=========

[](#smolquery)

A lightweight SQLite query builder and migration tool for PHP 8.1+.

Installation
------------

[](#installation)

```
composer require joby-lol/smol-query
```

About
-----

[](#about)

smolQuery provides a fluent interface for building SQLite queries and managing schema migrations. It is deliberately SQLite-only, which keeps the implementation simple and the API focused.

Basic Usage
-----------

[](#basic-usage)

```
use Joby\Smol\Query\DB;

$db = new DB('/path/to/database.db');

// Select
$rows = $db->select('users')
    ->where('active', 1)
    ->order('name ASC')
    ->limit(10)
    ->fetchAll();

// Insert
$db->insert('users')
    ->row(['name' => 'Alice', 'email' => 'alice@example.com'])
    ->execute();

// Update
$db->update('users')
    ->value('name', 'Alicia')
    ->where('id', 123)
    ->execute();

// Delete
$db->delete('users')
    ->where('id', 123)
    ->execute();
```

Select Queries
--------------

[](#select-queries)

### Fetching

[](#fetching)

```
$query = $db->select('users')->where('active', 1);

// Fetch one row (returns null when exhausted)
$row = $query->fetch();

// Fetch all rows as a generator
foreach ($query->fetchAll() as $row) {
    // ...
}

// Fetch a single column as a generator
foreach ($query->fetchColumn('email') as $email) {
    // ...
}

// Count results
$count = $query->count();
```

### Where Clauses

[](#where-clauses)

```
// Shorthand column = value
$query->where('status', 'active');

// Raw statement
$query->where('created_at > ?', $timestamp);

// Multiple parameters
$query->where('role = ? OR role = ?', ['admin', 'moderator']);

// NULL checks
$query->whereNull('deleted_at');
$query->whereNotNull('email');

// LIKE (case-sensitive by default, SQLite default is case-insensitive for ASCII)
$query->whereLike('name', 'alice%');
$query->whereLike('name', 'alice%', case_insensitive: true);
```

Multiple `where()` calls are AND'd together. For OR logic, use a single raw statement.

### Column selection, Order, Limit, Offset

[](#column-selection-order-limit-offset)

```
$db->select('users')
    ->column('id')
    ->column('name')
    ->order('name ASC')
    ->limit(20)
    ->offset(40)
    ->fetchAll();
```

### Hydration

[](#hydration)

By default, rows are returned as associative arrays. You can hydrate to objects using a callable or a class string.

```
// Callable hydrator
$query->hydrate(fn($row) => new User($row));

// Class string (uses PDO's FETCH_CLASS — note: not compatible with readonly properties)
$query->hydrate(User::class);

// Reset to arrays
$query->hydrate(null);
```

Parameters and values accept scalars, `Stringable` objects, backed enums (automatically unwrapped), callables (lazily evaluated), and `null`.

Insert Queries
--------------

[](#insert-queries)

```
// Single row
$db->insert('users')
    ->row(['name' => 'Alice', 'email' => 'alice@example.com'])
    ->execute();

// Multiple rows (must have identical keys)
$db->insert('users')
    ->row(['name' => 'Alice', 'email' => 'alice@example.com'])
    ->row(['name' => 'Bob', 'email' => 'bob@example.com'])
    ->execute();
```

`execute()` returns the number of rows inserted.

Update Queries
--------------

[](#update-queries)

```
// Single value
$db->update('users')
    ->value('name', 'Alicia')
    ->where('id', 123)
    ->execute();

// Multiple values
$db->update('users')
    ->values(['name' => 'Alicia', 'email' => 'alicia@example.com'])
    ->where('id', 123)
    ->execute();

// Initial values via constructor
$db->update('users', ['name' => 'Alicia'])->where('id', 123)->execute();
```

`execute()` returns the number of affected rows. Without a WHERE clause, an exception is thrown unless you pass `execute(without_where: true)`.

Delete Queries
--------------

[](#delete-queries)

```
$db->delete('users')->where('id', 123)->execute();
```

Same `without_where` safety guard as UPDATE.

Joins
-----

[](#joins)

```
// INNER JOIN — only rows with matches in both tables
$db->select('posts')
    ->join('users', 'users.id = posts.user_id')
    ->where('users.active', 1)
    ->fetchAll();

// LEFT JOIN — all rows from the left table, nulls for unmatched right table columns
$db->select('users')
    ->leftJoin('posts', 'posts.user_id = users.id')
    ->fetchAll();

// Multiple joins
$db->select('order_items')
    ->join('users', 'users.id = order_items.user_id')
    ->join('products', 'products.id = order_items.product_id')
    ->fetchAll();
```

By default, joined queries select `*` from all tables. If joined tables have columns with the same name, use explicit column selection to avoid collisions:

```
$db->select('posts')
    ->join('users', 'users.id = posts.user_id')
    ->column('posts.id')
    ->column('posts.title')
    ->column('users.name')
    ->fetchAll();
```

Migrations
----------

[](#migrations)

```
use Joby\Smol\Query\Migrator;

$migrator = new Migrator('/path/to/database.db');

// Programmatic migrations
$migrator->addMigration('001_create_users', '
    CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL
    );
    CREATE INDEX idx_users_email ON users(email);
');

// Directory of .sql files (sorted by filename, natural order)
$migrator->addMigrationDirectory(__DIR__ . '/migrations');

$migrator->run();
```

Migration files are named with a sortable prefix: `001_create_users.sql`, `002_add_email_index.sql`, etc. Each migration is recorded by name in a `_migrations` table. Migrations run inside a transaction and roll back on failure. Already-run migrations are skipped; out-of-order migrations throw an exception.

Multi-statement SQL files are fully supported.

### Custom Log Table

[](#custom-log-table)

```
$migrator = new Migrator('/path/to/database.db', log_table: '_schema_versions');
```

Requirements
------------

[](#requirements)

Fully tested on PHP 8.3+, static analysis for PHP 8.1+. Requires the `pdo_sqlite` and `sqlite3` PHP extensions (both enabled by default in most PHP installations).

License
-------

[](#license)

MIT License - See [LICENSE](LICENSE) file for details.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance86

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Total

3

Last Release

56d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4e606aa41ebfd7d82c365c6dc4ec158fb1b0bfff5f8dcbfad4ab53e3092e6a6?d=identicon)[jobyone](/maintainers/jobyone)

---

Top Contributors

[![joby-lol](https://avatars.githubusercontent.com/u/856610?v=4)](https://github.com/joby-lol "joby-lol (7 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/joby-smol-query/health.svg)

```
[![Health](https://phpackages.com/badges/joby-smol-query/health.svg)](https://phpackages.com/packages/joby-smol-query)
```

###  Alternatives

[doctrine/orm

Object-Relational-Mapper for PHP

10.2k285.3M6.2k](/packages/doctrine-orm)[jdorn/sql-formatter

a PHP SQL highlighting library

3.9k115.1M102](/packages/jdorn-sql-formatter)[illuminate/database

The Illuminate Database package.

2.8k52.4M9.4k](/packages/illuminate-database)[ramsey/uuid-doctrine

Use ramsey/uuid as a Doctrine field type.

90440.3M211](/packages/ramsey-uuid-doctrine)[reliese/laravel

Reliese Components for Laravel Framework code generation.

1.7k3.4M16](/packages/reliese-laravel)[wildside/userstamps

Laravel Userstamps provides an Eloquent trait which automatically maintains `created\_by` and `updated\_by` columns on your model, populated by the currently authenticated user in your application.

7511.7M13](/packages/wildside-userstamps)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
