The QueryBuilder class in JiFramework provides a fluent and intuitive interface for building and executing SQL queries using PDO. It supports common SQL operations such as SELECT, INSERT, UPDATE, and DELETE, along with clauses like WHERE, JOIN, GROUP BY, HAVING, ORDER BY, and more. This class simplifies database interactions by allowing developers to construct queries programmatically without writing raw SQL.
Method: __construct(string $connectionName = 'primary')
Description: Initializes a new instance of the QueryBuilder class, setting up the PDO connection based on the provided connection name.
Parameters:
- $connectionName (
string, optional): The name of the database connection to use. Defaults to'primary'.
Returns: An instance of the QueryBuilder class.
Usage:
use JIFramework\Core\App\App;
// Initialize the App
$app = new App();
// Initialize the QueryBuilder with the default connection
$db = $app->db;
OR
use JIFramework\Core\Database\QueryBuilder;
// Initialize the QueryBuilder with the default connection
$db = new QueryBuilder();
// Or specify a different connection that alreay setup in Config
$db = new QueryBuilder('connection_name');Explanation:
- The constructor obtains a PDO connection using the
DatabaseConnectionclass. - It allows you to work with multiple database connections if configured.
Method: table(string $table): self
Description: Sets the table to execute queries on.
Parameters:
- $table (
string): The name of the table.
Returns: The current instance of the QueryBuilder (for method chaining).
Usage:
$db->table('users');Explanation:
- This method specifies the table on which subsequent queries will be performed.
- It is usually the starting point for building a query.
Method: select(array|string $columns = ['*']): self
Description: Sets the columns to select in a SELECT query.
Parameters:
- $columns (
array|string, optional): The columns to select. Defaults to['*']. You can pass a string or an array of column names.
Returns: The current instance of the QueryBuilder.
Usage:
// Select all columns
$db->table('users')->select();
// Select specific columns
$db->table('users')->select('id', 'name', 'email');
// Using an array
$db->table('users')->select(['id', 'name', 'email']);Explanation:
- If no columns are specified, it defaults to selecting all columns (
*). - You can pass multiple columns as arguments or as an array.
Method: where(string|array $column, string|null $operator = null, mixed $value = null, string $boolean = 'AND'): self
Description: Adds a WHERE clause to the query.
Parameters:
- $column (
string|array): The column name or an associative array of column-value pairs. - $operator (
string|null, optional): The operator for the comparison (e.g.,'=','!=','>','<'). If omitted and$valueis provided, defaults to'='. - $value (
mixed, optional): The value to compare against. - $boolean (
string, optional): Logical operator to combine multiple conditions ('AND'or'OR'). Defaults to'AND'.
Returns: The current instance of the QueryBuilder.
Usage:
// Simple where clause
$db->table('users')->where('id', '=', 1);
// Operator defaults to '=' if omitted
$db->table('users')->where('email', '[email protected]');
// Multiple conditions
$db->table('users')
->where('status', 'active')
->where('age', '>', 18);
// Using an array of conditions
$db->table('users')->where([
'status' => 'active',
'role' => 'admin',
]);
// Using OR condition
$db->table('users')
->where('status', 'active')
->orWhere('role', 'admin');Explanation:
- Allows adding one or multiple
WHEREconditions. - If an array is provided as
$column, it iterates over each key-value pair. - Supports combining conditions with
'AND'and'OR'.
Method: orWhere(string|array $column, string|null $operator = null, mixed $value = null): self
Description: Adds an OR WHERE clause to the query.
Parameters:
- $column (
string|array): The column name or an associative array of column-value pairs. - $operator (
string|null, optional): The operator for the comparison. - $value (
mixed, optional): The value to compare against.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('users')
->where('status', 'active')
->orWhere('role', 'admin');Explanation:
- Functions similarly to
where, but combines the condition using'OR'.
Method: join(string $table, string $first, string $operator, string $second, string $type = 'INNER'): self
Description: Adds a JOIN clause to the query.
Parameters:
- $table (
string): The table to join. - $first (
string): The first column in the join condition. - $operator (
string): The operator for the join condition. - $second (
string): The second column in the join condition. - $type (
string, optional): The type of join ('INNER','LEFT','RIGHT'). Defaults to'INNER'.
Returns: The current instance of the QueryBuilder.
Usage:
// Inner join
$db->table('orders')
->join('users', 'orders.user_id', '=', 'users.id');
// Left join
$db->table('orders')
->join('users', 'orders.user_id', '=', 'users.id', 'LEFT');
// Right join
$db->table('orders')
->join('users', 'orders.user_id', '=', 'users.id', 'RIGHT');Explanation:
- Supports different types of joins.
- Joins are useful for combining rows from two or more tables.
Method: leftJoin(string $table, string $first, string $operator, string $second): self
Description: Adds a LEFT JOIN clause to the query.
Parameters:
- Same as
join, but the$typeis fixed to'LEFT'.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('orders')
->leftJoin('users', 'orders.user_id', '=', 'users.id');Method: rightJoin(string $table, string $first, string $operator, string $second): self
Description: Adds a RIGHT JOIN clause to the query.
Parameters:
- Same as
join, but the$typeis fixed to'RIGHT'.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('orders')
->rightJoin('users', 'orders.user_id', '=', 'users.id');Method: limit(int $limit): self
Description: Sets the LIMIT for the query.
Parameters:
- $limit (
int): The maximum number of records to retrieve.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('users')->limit(10);Explanation:
- Limits the number of records returned by a
SELECTquery.
Method: offset(int $offset): self
Description: Sets the OFFSET for the query.
Parameters:
- $offset (
int): The number of records to skip.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('users')->limit(10)->offset(20);Explanation:
- Useful for pagination in combination with
limit.
Method: orderBy(string $column, string $direction = 'ASC'): self
Description: Adds an ORDER BY clause to the query.
Parameters:
- $column (
string): The column to sort by. - $direction (
string, optional): The sort direction ('ASC'or'DESC'). Defaults to'ASC'.
Returns: The current instance of the QueryBuilder.
Usage:
// Order by created_at descending
$db->table('users')->orderBy('created_at', 'DESC');Explanation:
- You can chain multiple
orderBycalls to sort by multiple columns.
Method: groupBy(array|string $columns): self
Description: Adds a GROUP BY clause to the query.
Parameters:
- $columns (
array|string): The column(s) to group by.
Returns: The current instance of the QueryBuilder.
Usage:
// Group by status
$db->table('users')->groupBy('status');
// Group by multiple columns
$db->table('orders')->groupBy(['user_id', 'status']);Explanation:
- Groups the result set by one or more columns.
Method: having(string $column, string $operator, mixed $value, string $boolean = 'AND'): self
Description: Adds a HAVING clause to the query.
Parameters:
- $column (
string): The column or aggregate function to apply the condition on. - $operator (
string): The operator for the comparison. - $value (
mixed): The value to compare against. - $boolean (
string, optional): Logical operator to combine multiple conditions ('AND'or'OR'). Defaults to'AND'.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('orders')
->select('user_id', 'SUM(total) as total_spent')
->groupBy('user_id')
->having('total_spent', '>', 1000);Explanation:
HAVINGis used to filter results after aggregation (GROUP BY).
Method: orHaving(string $column, string $operator, mixed $value): self
Description: Adds an OR HAVING clause to the query.
Parameters:
- Same as
having, but combines the condition using'OR'.
Returns: The current instance of the QueryBuilder.
Usage:
$db->table('orders')
->select('user_id', 'SUM(total) as total_spent')
->groupBy('user_id')
->having('total_spent', '>', 1000)
->orHaving('total_spent', '<', 100);Method: get(): array
Description: Executes the query and retrieves all matching records as an array.
Parameters: None
Returns: An array of associative arrays representing the result set.
Usage:
$users = $db->table('users')->where('status', 'active')->get();
foreach ($users as $user) {
echo $user['name'] . '
';
}Explanation:
- Executes the built query and fetches all results.
- Resets the internal state after execution.
Method: first(): array|false
Description: Executes the query and retrieves the first matching record.
Parameters: None
Returns: An associative array representing the first result, or false if no records are found.
Usage:
$user = $db->table('users')->where('id', 1)->first();
if ($user) {
echo 'User found: ' . $user['name'];
} else {
echo 'User not found.';
}Explanation:
- Automatically sets
LIMIT 1on the query. - Useful when expecting a single record.
Method: value(string $column)
Description: Fetches a single column value from the first matching record.
Parameters:
- $column (
string): The column name to retrieve.
Returns: The value of the specified column, or null if not found.
Usage:
$email = $db->table('users')->where('id', 1)->value('email');
echo 'User email: ' . $email;Explanation:
- Simplifies retrieving a single value from the database.
Method: count(): int
Description: Counts the number of records matching the query.
Parameters: None
Returns: An integer representing the count.
Usage:
$totalUsers = $db->table('users')->where('status', 'active')->count();
echo 'Total active users: ' . $totalUsers;Explanation:
- Executes a
SELECT COUNT(*)query. - Useful for pagination or statistics.
Method: insert(array $data): bool
Description: Inserts a new record into the database.
Parameters:
- $data (
array): An associative array of column-value pairs to insert.
Returns: bool indicating whether the insertion was successful.
Usage:
$result = $db->table('users')->insert([
'name' => 'Jane Doe',
'email' => '[email protected]',
'password' => password_hash('secret', PASSWORD_DEFAULT),
]);
if ($result) {
echo 'User inserted successfully.';
} else {
echo 'Failed to insert user.';
}Explanation:
- Prepares an
INSERT INTOstatement. - Binds the values securely to prevent SQL injection.
Method: update(array $data): int
Description: Updates records in the database matching the query.
Parameters:
- $data (
array): An associative array of column-value pairs to update.
Returns: An integer representing the number of affected rows.
Usage:
$rowsUpdated = $db->table('users')
->where('id', 1)
->update([
'email' => '[email protected]',
]);
echo 'Rows updated: ' . $rowsUpdated;Explanation:
- Prepares an
UPDATEstatement withSETclauses. - Requires a
WHEREclause to avoid updating all records unintentionally.
Method: delete(): int
Description: Deletes records from the database matching the query.
Parameters: None
Returns: An integer representing the number of affected rows.
Usage:
$rowsDeleted = $db->table('users')->where('id', 1)->delete();
echo 'Rows deleted: ' . $rowsDeleted;Explanation:
- Prepares a
DELETE FROMstatement. - Requires a
WHEREclause to avoid deleting all records.
Method: execute(): int
Description: Executes a query without fetching results. Useful for INSERT, UPDATE, or DELETE statements when you need the number of affected rows.
Parameters: None
Returns: An integer representing the number of affected rows.
Usage:
$affectedRows = $db->query("DELETE FROM users WHERE status = 'inactive'")->execute();
echo 'Inactive users deleted: ' . $affectedRows;Explanation:
- Executes the query and returns the number of affected rows.
- Can be used with raw SQL queries.
Method: toSql(): string
Description: Generates the SQL representation of the query without executing it.
Parameters: None
Returns: A string containing the SQL query.
Usage:
$sql = $db->table('users')->where('status', 'active')->toSql();
echo 'Generated SQL: ' . $sql;Explanation:
- Useful for debugging or logging purposes.
- Note that parameter bindings are not substituted in the string.
Method: query(string $sql): self
Description: Sets a raw SQL query to be executed.
Parameters:
- $sql (
string): The raw SQL query.
Returns: The current instance of the QueryBuilder.
Usage:
$db->query('SELECT * FROM users WHERE status = :status')->bind(':status', 'active');Explanation:
- Allows you to execute custom SQL queries while still benefiting from parameter binding.
Method: bind(string|array $param, mixed $value = null, int|null $type = null): self
Description: Binds a parameter to the query. Supports binding multiple parameters at once.
Parameters:
- $param (
string|array): The parameter name (e.g.,':id') or an associative array of parameters. - $value (
mixed, optional): The value to bind if$paramis a string. - $type (
int|null, optional): The PDO parameter type (e.g.,PDO::PARAM_INT). If omitted, it is detected automatically.
Returns: The current instance of the QueryBuilder.
Usage:
// Binding a single parameter
$db->query('SELECT * FROM users WHERE id = :id')->bind(':id', 1);
// Binding multiple parameters
$db->query('SELECT * FROM users WHERE id = :id AND status = :status')
->bind([
':id' => 1,
':status' => 'active',
]);Explanation:
- Supports both single and multiple parameter bindings.
- Automatically detects the PDO parameter type if not specified.
Method: lastInsertId(): string
Description: Retrieves the ID of the last inserted row.
Parameters: None
Returns: A string representing the last inserted ID.
Usage:
$db->table('users')->insert([
'name' => 'John Doe',
'email' => '[email protected]',
]);
$lastId = $db->lastInsertId();
echo 'Last inserted ID: ' . $lastId;Explanation:
- Useful after an
INSERToperation to get the auto-incremented ID.
Complex Query Examples
Example 1: Complex Select with Joins, Group By, Having, and Order By
$db = new QueryBuilder();
$results = $db->table('orders')
->select('users.name', 'users.email', 'SUM(orders.total) as total_spent')
->join('users', 'orders.user_id', '=', 'users.id')
->where('orders.status', 'completed')
->groupBy('users.id')
->having('total_spent', '>', 1000)
->orderBy('total_spent', 'DESC')
->limit(10)
->get();
foreach ($results as $row) {
echo $row['name'] . ' spent ' . $row['total_spent'] . '
';
}Explanation:
- Selects users who have completed orders with total spending over $1000.
- Joins
ordersanduserstables. - Groups the results by user.
- Orders the results by total spending in descending order.
- Limits the results to the top 10 users.
Example 2: Subquery with Raw SQL
$db = new QueryBuilder();
$subQuery = '(SELECT AVG(rating) FROM reviews WHERE reviews.product_id = products.id)';
$results = $db->table('products')
->select('products.name', 'products.price', "{$subQuery} as average_rating")
->where('products.price', '>', 50)
->orderBy('average_rating', 'DESC')
->get();
foreach ($results as $product) {
echo $product['name'] . ' has an average rating of ' . $product['average_rating'] . '
';
}Explanation:
- Calculates the average rating for each product using a subquery.
- Selects products priced over $50.
- Orders the products by their average rating.
Example 3: Transactions
$db = new QueryBuilder();
try {
$db->beginTransaction();
// Update user balance
$db->table('users')
->where('id', $userId)
->update(['balance' => $newBalance]);
// Insert transaction record
$db->table('transactions')
->insert([
'user_id' => $userId,
'amount' => $amount,
'type' => 'debit',
'created_at' => date('Y-m-d H:i:s'),
]);
$db->commit();
echo 'Transaction completed successfully.';
} catch (Exception $e) {
$db->rollBack();
echo 'Transaction failed: ' . $e->getMessage();
}Explanation:
- Demonstrates using transactions to ensure data integrity.
- If any operation fails, the entire transaction is rolled back.
Example 4: Multiple Joins with Aliases and Complex Conditions
$db = new QueryBuilder();
// Fetch orders along with customer and product details where the order amount is greater than $500
$results = $db->table('orders as o')
->select('o.order_id', 'c.name as customer_name', 'p.product_name', 'o.total_amount')
->join('customers as c', 'o.customer_id', '=', 'c.customer_id')
->join('order_items as oi', 'o.order_id', '=', 'oi.order_id')
->join('products as p', 'oi.product_id', '=', 'p.product_id')
->where('o.total_amount', '>', 500)
->orderBy('o.order_date', 'DESC')
->get();
foreach ($results as $order) {
echo "Order ID: {$order['order_id']} - Customer: {$order['customer_name']} - Product: {$order['product_name']} - Total: {$order['total_amount']}
";
}Explanation:
- Tables and Aliases: The main table
ordersis aliased aso. - Joins:
- Join
customerstable ascto get customer details. - Join
order_itemstable asoito get items in each order. - Join
productstable aspto get product details. - Conditions:
- Only select orders where
o.total_amountis greater than 500. - Ordering:
- Orders are sorted by
o.order_datein descending order.
Example 5: Using Subqueries and EXISTS Clause
$db = new QueryBuilder();
// Fetch users who have not placed any orders
$subQuery = $db->table('orders')
->select('user_id')
->whereRaw('orders.user_id = users.id')
->toSql();
$usersWithoutOrders = $db->table('users')
->select('users.id', 'users.name', 'users.email')
->whereRaw("NOT EXISTS ({$subQuery})")
->get();
foreach ($usersWithoutOrders as $user) {
echo "User ID: {$user['id']} - Name: {$user['name']} - Email: {$user['email']}
";
}Explanation:
- Subquery:
- Creates a subquery to select
user_idfromorderswhereorders.user_idequalsusers.id. - NOT EXISTS:
- Uses
NOT EXISTSto find users who do not have matching records in theorderstable. - whereRaw:
- Since the
QueryBuildermay not have awhereExistsmethod,whereRawis used to insert raw SQL conditions.
Example 9: Pagination with OFFSET and LIMIT
$db = new QueryBuilder();
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;
$results = $db->table('articles')
->select('id', 'title', 'published_at')
->where('status', 'published')
->orderBy('published_at', 'DESC')
->limit($perPage)
->offset($offset)
->get();
foreach ($results as $article) {
echo "Article ID: {$article['id']} - Title: {$article['title']}
";
}Explanation:
- Pagination Logic:
- Calculates
$offsetbased on the current page. - Query:
- Retrieves a specific number of articles per page.
- Ordering:
- Orders articles by publication date.
Example 11: Union of Two Queries
$db = new QueryBuilder();
$activeUsers = $db->table('users')
->select('id', 'name', 'email')
->where('status', 'active')
->toSql();
$pendingUsers = $db->table('users')
->select('id', 'name', 'email')
->where('status', 'pending')
->toSql();
$unionQuery = "{$activeUsers} UNION {$pendingUsers} ORDER BY name ASC";
$results = $db->query($unionQuery)->get();
foreach ($results as $user) {
echo "User ID: {$user['id']} - Name: {$user['name']}
";
}Explanation:
- Union of Queries:
- Combines results from two queries using
UNION. - Ordering:
- Orders the combined results by name.