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.

Maintainers

Package info

github.com/sim-soft/fliq

pkg:composer/simsoft/fliq

Statistics

Installs: 3

Dependents: 1

Suggesters: 1

Stars: 1

Open Issues: 0

2.0.0 2026-05-30 21:42 UTC

This package is auto-updated.

Last update: 2026-05-30 23:12:58 UTC


README

Fast, Lightweight, Independent Query Builder

License: MIT Docs

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, one Connection::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

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

  1. Getting Started โ€” Connections, drivers, raw queries, DB facade, monitoring
  2. Query Builder โ€” Fluent API, conditions, joins, JSON, aggregation, scopes, unions
  3. Active Record โ€” Models, CRUD, casting, hooks, events, soft deletes, timestamps
  4. Relations โ€” hasOne, hasMany, via, viaTable, eager loading, whereHas
  5. Advanced Features โ€” Caching, pagination, global scopes, batch operations, read/write split
  6. Collections โ€” Lazy iteration, filter, map, reduce, indexBy, groupBy, batch processing
  7. Comparison โ€” Feature comparison with Eloquent, Doctrine, Yii3, Cycle, Propel
  8. Cheatsheet โ€” Quick reference for all common operations

License

FLIQ is licensed under the MIT License. See the LICENSE file for details.