simsoft / fliq
FLIQ โ Fast, Lightweight, Independent Query Builder. High-performance Active Record ORM for MySQL/MariaDB, PostgreSQL, and SQLite. Zero-allocation query builder, nested eager loading, soft deletes, and N+1 detection built-in.
Requires
- php: ^8.4
- ext-pdo: *
Requires (Dev)
- phpmd/phpmd: ^2.15
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- simsoft/validator: ^3.0
- squizlabs/php_codesniffer: ^3.13
Suggests
- ext-mysqli: Required for MySQLi driver support
- simsoft/validator: For model validation support
This package is auto-updated.
Last update: 2026-05-30 23:12:58 UTC
README
Fast, Lightweight, Independent Query Builder
A high-performance Active Record / ORM for MySQL, MariaDB, PostgreSQL, and SQLite. Zero framework dependencies, minimal footprint, maximum speed.
๐ Documentation ยท GitHub
use Simsoft\DB\Model; class User extends Model { protected string $table = 'user'; protected array $fillable = ['name', 'email', 'status']; } // Query with fluent builder $users = User::find() ->where('status', 'active') ->with('posts.comments') // nested eager loading ->orderBy('name') ->get(); // CRUD $user = new User(['name' => 'John', 'email' => 'john@example.com']); $user->save();
Why FLIQ?
The name says it all โ Fast, Lightweight, Independent Query Builder.
- Fast โ One object per query, zero-allocation fast path, prepared statement caching. No other PHP ORM builds query this lean.
- Lightweight โ ~100KB install size, zero dependencies. No service containers, no config files, no boot process.
- Independent โ Standalone library with no framework coupling. One
composer require, oneConnection::add()call, done. - Query Builder โ Fluent, expressive API that compiles directly to optimized SQL without intermediate object layers.
When NOT to Choose FLIQ
- You need MSSQL or Oracle support
- You need schema migrations (use Phinx or doctrine/migrations)
- You need a Data Mapper pattern (use Doctrine or Cycle ORM)
- You need a massive ecosystem of community packages (use Eloquent)
For a detailed feature-by-feature comparison with Eloquent, Doctrine, Yii3, Cycle ORM, and Propel, see the Comparison Guide.
Requirements
- PHP 8.4+
- MySQL 5.7+ / MariaDB 10.3+ / PostgreSQL 12+ / SQLite 3.39+
- ext-pdo (required) or ext-mysqli (optional alternative for MySQL)
Install
composer require simsoft/fliq
Quick Start
1. Configure Connection
require "vendor/autoload.php"; use Simsoft\DB\Connection; Connection::add('mysql', [ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'my_app', 'username' => 'root', 'password' => '', 'charset' => 'utf8mb4', ]);
2. Define a Model
use Simsoft\DB\Model; use Simsoft\DB\Relation; class Post extends Model { protected string $table = 'post'; protected array $fillable = ['title', 'content', 'user_id']; public function author(): Relation { return $this->hasOne(User::class, ['id' => 'user_id']); } public function comments(): Relation { return $this->hasMany(Comment::class, ['post_id' => 'id']); } }
3. Use It
// Find by primary key $post = Post::findByPk(1); // Query with conditions $posts = Post::find() ->where('status', 'published') ->with('author', 'comments') ->orderBy('created_at', 'DESC') ->limit(10) ->get(); // Create $post = new Post(['title' => 'Hello', 'content' => 'World']); $post->save(); // Update $post->title = 'Updated'; $post->save(); // Delete $post->delete();
Features
- Zero-Allocation Query Building
- Nested Eager Loading
- Conditional Queries
- JSON Column Queries
- Multi-Column Conditions
- Transactions
- Soft Deletes & Timestamps
- Model Events
- Query Result Caching
- Read/Write Connection Splitting
- Development Tools
Zero-Allocation Query Building
The most common query patterns (where, in, like, between, orderBy, select) build SQL strings directly without creating intermediate objects. One ActiveQuery object handles everything.
Nested Eager Loading
// 4 queries total, regardless of record count $users = User::find()->with('posts.comments.author')->get();
Conditional Queries
$users = User::find() ->when($search !== null, fn($q) => $q->like('name', "%$search%")) ->unless($isAdmin, fn($q) => $q->where('published', true)) ->get();
JSON Column Queries
// Auto JSON extraction via -> notation (works in where, in, orderBy, etc.) User::find()->where('preferences->dining->meal', 'salad')->get(); User::find()->in('preferences->dining->meal', ['pasta', 'salad'])->get(); User::find()->orderBy('meta->score', 'DESC')->get(); // JSON methods User::find()->jsonContains('tags', 'php')->get(); // array contains value User::find()->jsonNotContains('tags', 'java')->get(); // array excludes value User::find()->jsonHas('meta->address')->get(); // key exists User::find()->jsonMissing('meta->foo')->get(); // key missing User::find()->jsonLength('tags', '>', 2)->get(); // array length // Aliases (whereJson* style) User::find()->whereJsonContains('tags', 'php')->get(); User::find()->whereJsonDoesntContain('tags', 'java')->get(); User::find()->whereJsonContainsKey('meta->address')->get(); User::find()->whereJsonDoesntContainKey('meta->foo')->get(); User::find()->whereJsonLength('tags', '>', 2)->get();
Multi-Column Conditions
// Match ANY column (OR logic) User::find()->whereAny(['name', 'email', 'phone'], 'like', '%john%')->get(); // โ WHERE (name LIKE ? OR email LIKE ? OR phone LIKE ?) // Match ALL columns (AND logic) Post::find()->whereAll(['title', 'body'], 'like', '%Laravel%')->get(); // โ WHERE (title LIKE ? AND body LIKE ?) // Match NONE of the columns Post::find()->whereNone(['title', 'body'], 'like', '%spam%')->get(); // โ WHERE NOT (title LIKE ? OR body LIKE ?)
Transactions
User::transaction(function () { $user = new User(['name' => 'John', 'email' => 'john@example.com']); $user->save(); $post = new Post(['user_id' => $user->id, 'title' => 'First Post']); $post->save(); return true; // commit }); // Return false (or don't return true) to roll back
Soft Deletes & Timestamps
class User extends Model { use SoftDeletes, Timestamps; protected string $table = 'user'; } $user->delete(); // sets deleted_at $user->restore(); // clears deleted_at User::withTrashed()->get(); // includes deleted
Model Events
// Register event listeners User::on('creating', function (User $user) { $user->slug = strtolower($user->name); }); User::on('deleting', function (User $user) { if ($user->role === 'admin') return false; // cancel deletion }); // Observer class User::observe(new AuditObserver());
Query Result Caching
use Simsoft\DB\Cache\QueryCache; use Simsoft\DB\Cache\ArrayCache; QueryCache::setDriver(new ArrayCache()); // Cache results for 60 seconds $users = User::find()->where('active', 1)->cache(60)->get();
Read/Write Connection Splitting
Connection::add('mysql', [ 'driver' => 'mysql', 'database' => 'myapp', 'read' => ['host' => 'replica.db.internal'], 'write' => ['host' => 'primary.db.internal'], ]); // SELECT auto-routes to read, INSERT/UPDATE/DELETE to write
Development Tools
// N+1 detection QueryMonitor::enable(); // Query logging with timing QueryLogger::enable(); $queries = QueryLogger::getQueries(); $slowest = QueryLogger::getSlowestQuery(); // Index advisor IndexAdvisor::suggestSQL();
Documentation
๐ Read the full documentation
- Getting Started โ Connections, drivers, raw queries, DB facade, monitoring
- Query Builder โ Fluent API, conditions, joins, JSON, aggregation, scopes, unions
- Active Record โ Models, CRUD, casting, hooks, events, soft deletes, timestamps
- Relations โ hasOne, hasMany, via, viaTable, eager loading, whereHas
- Advanced Features โ Caching, pagination, global scopes, batch operations, read/write split
- Collections โ Lazy iteration, filter, map, reduce, indexBy, groupBy, batch processing
- Comparison โ Feature comparison with Eloquent, Doctrine, Yii3, Cycle, Propel
- Cheatsheet โ Quick reference for all common operations
License
FLIQ is licensed under the MIT License. See the LICENSE file for details.