Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Lessql Laravel Package

morris/lessql

LessQL is a lightweight, fast PHP database layer with a simple NotORM-style API. Query tables, traverse associations and save data with minimal code, no configuration, and no heavy ORM overhead.

Deep Wiki
Context7

Getting Started

LessQL is not a Laravel package — there is no service provider, facade, config file, or migration support. Treat it as a standalone PDO wrapper you wire in yourself.

  1. composer require morris/lessql
  2. Bind a singleton in a service provider, reusing Laravel's already-open connection so you don't get a second pool:
$this->app->singleton(\LessQL\Database::class, function () {
    $pdo = DB::connection()->getPdo();
    $db  = new \LessQL\Database($pdo);
    // MySQL only — LessQL quotes identifiers with ANSI double quotes by default
    $db->setIdentifierDelimiter('`');
    return $db;
});
  1. First use case: a read-heavy dashboard or report where you want rows, not models.
$posts = app(\LessQL\Database::class)
    ->post()
    ->where('is_published', 1)
    ->orderBy('date_published', 'DESC')
    ->limit(20);

foreach ($posts as $post) {
    echo $post['title'];          // ArrayAccess
    echo $post->user()->name;     // reference traversal
}

Read the three classes in this order and you've read the whole library: LessQL\Database (connection + table access), LessQL\Result (lazy iterable query builder), LessQL\Row (array-backed row with save()/delete()). Everything else is inference conventions.

Implementation Patterns

Table access as a query entry point. $db->post() returns a Result; nothing executes until you iterate, first(), or count(). Chain where, orderBy, limit, offset, select, groupBy as needed. This is the closest thing to a "model" LessQL has — there are no classes per table.

Reference traversal instead of relationship definitions. LessQL guesses relationships from column names: a column user_id on post makes $post->user() resolve to the user table; going the other way, $user->post() returns rows whose user_id matches. No hasMany/belongsTo declarations, no registration step. Many-to-many has no shorthand — walk the association table explicitly ($post->post_category()->categorie()-style chaining) or drop to raw SQL.

Persistence is row-centric and dirty-tracked.

$post = $db->post()->createRow(['title' => 'Hi', 'user_id' => 1]);
$post->save();                 // INSERT, PK populated
$post->title = 'Hello';
$post->save();                 // UPDATE of changed columns
$post->delete();

$db->post()->where('is_published', 0)->delete();   // bulk
$db->post()->insert(['title' => 'Bulk']);          // no dirty tracking

Wrap multi-step writes in $db->begin() / commit() / rollback().

Manual eager loading to survive N+1. This is the pattern you'll write over and over:

$posts = $db->post()->where('is_published', 1)->getRows(); // or iterate once into an array
$userIds = array_unique(array_column($posts, 'user_id'));
$users = $db->user()->where('id', $userIds)->getRows();    // check whether your version supports array values here
$byId = array_column($users, null, 'id');

foreach ($posts as $post) {
    $author = $byId[$post['user_id']] ?? null;
}

If the builder doesn't support IN conveniently in your version, $db->query('SELECT ... WHERE id IN (...)', $params) is the honest escape hatch — LessQL's raw query path returns the same Result/Row types.

Where it actually earns its place in a Laravel app: artisan commands, importers, legacy-database read models, CSV/report generation, and thin admin screens against a schema you don't control. Keep Eloquent for anything with events, casts, soft deletes, or validation. Mixing both against the same connection is fine; mixing transaction bookkeeping is not (see below).

Testing. new \LessQL\Database(new \PDO('sqlite::memory:')) against a schema you build by hand is fast and needs no Laravel bootstrapping. Good for unit-testing query logic in isolation from Eloquent.

Gotchas and Tips

  • MySQL will throw syntax errors out of the box. Default identifier delimiter is ", which MySQL only accepts in ANSI_QUOTES mode. Always call setIdentifierDelimiter('')`.
  • Schema conventions are hard requirements, not defaults. Reference inference needs <name>_id columns and a table literally named <name>. Any pluralized/legacy schema (users, tbl_user) silently breaks traversal. Workarounds: SQL views that rename tables/columns to the convention, or explicit raw queries. Don't fight the inference.
  • No eager loading, no lazy-loading guard. $post->user() inside a loop is one query per row, every time. There's no with(), no preventLazyLoading, no N+1 warning. Budget for this in code review.
  • save() is insert-or-update with no mass-assignment protection. Passing $request->all() into insert() or createRow() writes every key. Always whitelist with $request->only([...]).
  • Rows are dumb data. No Carbon, no casts, no JSON columns, no accessors, no observers, no soft deletes, no updated_at/created_at automation. Dates come back as strings; convert at the boundary.
  • Transactions don't compose with Laravel's. DB::beginTransaction() and LessQL's $db->begin() talk to the same PDO but maintain separate nesting counters. Pick one per code path, or a rollback in one layer will leave the other confused.
  • Laravel's query log is blind to it. DB::listen(), Telescope, and DB::enableQueryLog() won't see LessQL statements because they bypass the query builder. For debugging, wrap the PDO instance in a decorator that logs prepare/execute, or enable the database's own general log.
  • Primary keys are assumed to be id. Composite keys and non-id PKs need setPrimaryKey on the table (check the source for the exact signature in your version) or save() will misbehave.
  • where('col', null) should render IS NULL — verify this in your version rather than assuming; hand-write where('col IS NULL')-style raw fragments if the builder doesn't cover your case.
  • Unmaintained since May 2020. No PHP 8.x compatibility guarantees, no security patches, no upstream fixes. Pin the exact version in composer.json, keep the surface area small (read paths and simple writes), and put a thin repository class between your app and LessQL\Database so replacing it later is a one-file change rather than a grep across the codebase.
  • Extension points are thin: subclass Database and Row for logging, type coercion, or a shared save() hook; that's about the ceiling. If you find yourself wanting more, that's the signal to move that table back to Eloquent.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console