Query Builder Guide

This guide covers in detail how to interact with the database using the **Hybrid Active Record** pattern (Constitution Section 2) in the phpLiteCore framework.

The mechanism relies on BaseModel.php which utilizes BaseQueryBuilder.php and a set of Traits (QueryBuilderWhereTraits, QueryBuilderLikeTraits) to provide a fluent interface.

Data Contract: All query methods that return records (like get, first, paginate) hydrate the results into **Objects** of the model type. Always remember to use $user->name, not $user['name'].

1. Retrieving Data (SELECT)

all, find, first

These are the basic methods for fetching records.

// Get all records from the 'users' table
$users = User::all();

// Find a record by its primary key (e.g., 'id')
$user = User::find(1);

// Get the *first* record matching a query
$user = User::where('status', 'active')->first();

Building Queries (Chaining)

You can build complex queries by chaining methods together, terminating with get() (to fetch multiple records) or first() (to fetch a single record).

$activeAdmins = User::where('role', 'admin')
                     ->where('status', 'active')
                     ->orderBy('created_at', 'DESC')
                     ->limit(10)
                     ->get();

foreach ($activeAdmins as $admin) {
    echo $admin->name;
}

Aggregates

The builder currently provides a count() method.

// Get the total count of all users
$totalUsers = User::count();

// Get the count of users matching a WHERE clause
$adminCount = User::where('role', 'admin')->count();

Note: The found() method is deprecated and will be removed in a future release. Please use count() instead.

Pagination (Paginate)

The paginate() method automatically calculates the total, slices the results, and fetches items for the current page.

// Inside PostController::index()
$itemsPerPage = 5;
$currentPage = $_GET['page'] ?? 1;

// This returns a structured array: ['paginator' => Paginator, 'items' => [Post, ...]]
$paginationData = Post::orderBy('created_at', 'DESC')
                      ->paginate($itemsPerPage, $currentPage);

// Get the post objects for the current page
$posts = $paginationData['items'];

// Get the Paginator object to render links
$paginator = $paginationData['paginator'];

2. WHERE Clauses (Full)

The query builder offers a flexible interface for constructing complex WHERE clauses, thanks to QueryBuilderWhereTraits.php.

Where (Basic & Array)

The simplest form of where accepts a column and value (implicitly assuming '='), or a column, operator, and value.

// WHERE `role` = 'admin'
$admins = User::where('role', 'admin')->get();

// WHERE `points` > 100
$highScoreUsers = User::where('points', '>', 100)->get();

You can also pass an array to create multiple AND conditions using the '=' operator.

// WHERE `role` = 'admin' AND `status` = 'active'
$activeAdmin = User::where([
    'role' => 'admin',
    'status' => 'active'
])->first();

orWhere

To chain conditions using OR.

$users = User::where('status', 'active')
                     ->orWhere('role', 'admin')
                     ->get();
// SQL: SELECT * FROM `users` WHERE `status` = ? OR `role` = ?

Where (Nested - Groups)

To group constraints within parentheses (...), pass a Closure to where or orWhere. The Closure will receive a new query builder instance that you can use to add the nested constraints.

// WHERE `status` = 'active' AND (`role` = 'admin' OR `role` = 'moderator')
$users = User::where('status', 'active')
                     ->where(function ($query) {
                         $query->where('role', 'admin')
                               ->orWhere('role', 'moderator');
                     })
                     ->get();

Use orWhere with a Closure to create an OR (...) group.

// WHERE `status` = 'active' OR (`created_at` > '2025-01-01' AND `role` = 'guest')
$users = User::where('status', 'active')
                     ->orWhere(function ($query) {
                         $query->where('created_at', '>', '2025-01-01')
                               ->where('role', 'guest');
                     })
                     ->get();

whereIn / whereNotIn

To check if a column's value is within an array of values.

$ids = [1, 5, 10];

// WHERE `id` IN (1, 5, 10)
$users = User::whereIn('id', $ids)->get();

// WHERE `status` NOT IN ('banned', 'pending')
$users = User::whereNotIn('status', ['banned', 'pending'])->get();

// You can also use orWhereIn / orWhereNotIn
$users = User::where('active', 1)
                     ->orWhereIn('role', ['admin', 'superadmin'])->get();

whereBetween / orWhereBetween

To check if a column's value is within a given range.

// WHERE `points` BETWEEN 100 AND 200
$users = User::whereBetween('points', 100, 200)->get();

// WHERE `level` < 10 OR `points` BETWEEN 500 AND 1000
$users = User::where('level', '<', 10)
                     ->orWhereBetween('points', 500, 1000)->get();

3. LIKE Clauses

QueryBuilderLikeTraits.php provides helper methods for common LIKE operations.

whereStarts

// WHERE `name` LIKE 'Jo%'
$users = User::whereStarts('name', 'Jo')->get();

whereHas (Contains)

// WHERE `email` LIKE '%@example.com%'
$users = User::whereHas('email', '@example.com')->get();

whereEnds

// WHERE `filename` LIKE '%.pdf'
$files = File::whereEnds('filename', '.pdf')->get();

These methods also support passing an array to search for multiple values using OR.

// WHERE (`email` LIKE '%@yahoo.com%' OR `email` LIKE '%@hotmail.com%')
$users = User::whereHas('email', ['@yahoo.com', '@hotmail.com'])->get();

4. Insert, Update, Delete

Creating (Insert)

Create a new Model() instance and call save().

// 1. Create a new Post object
$post = new Post([
    'title'   => 'New Post Title',
    'body'    => 'This is the post body.',
    'user_id' => 1,
]);

// 2. Save (executes INSERT)
$post->save();

// The $post object now contains the new 'id'
echo "New post created with ID: " . $post->id;

Updating (Update)

Use find(), modify properties, then call save().

// 1. Find an existing post
$post = Post::find(10);

if ($post) {
    // 2. Modify properties
    $post->title = 'This is the Updated Title';
    $post->status = 'published';

    // 3. Save (executes UPDATE only for changed fields)
    $post->save();
}

Deleting (Delete)

Use the static delete() method chained after a where() clause.

// Delete a specific post by ID
$affectedRows = Post::where('id', '=', 10)->delete();

// Delete all posts by a specific user
$affectedRows = Post::where('user_id', 5)->delete();

5. Raw Queries

For very complex scenarios not covered by the builder, you can use the Database instance directly for raw queries.

Warning: Raw queries bypass automatic protection. Always use bindings to prevent SQL injection vulnerabilities.
// Use the '$app->db' service instance

// SELECT query with bindings
$sql = "SELECT * FROM users WHERE role = ? AND status = ?";
$bindings = ['admin', 'active'];

$statement = $this->app->db->raw($sql, $bindings);
$results = $statement->fetchAll(\PDO::FETCH_ASSOC);

// INSERT/UPDATE query (returns affected row count)
$sql = "UPDATE users SET last_login = NOW() WHERE id = ?";
$statement = $this->app->db->raw($sql, [1]);
$affected = $statement->rowCount();