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
DatabaseConnection
class. - 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$value
is 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
WHERE
conditions. - 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$type
is 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$type
is 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
SELECT
query.
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
orderBy
calls 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:
HAVING
is 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 1
on 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 INTO
statement. - 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
UPDATE
statement withSET
clauses. - Requires a
WHERE
clause 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 FROM
statement. - Requires a
WHERE
clause 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$param
is 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
INSERT
operation 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
orders
andusers
tables. - 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
orders
is aliased aso
. - Joins:
- Join
customers
table asc
to get customer details. - Join
order_items
table asoi
to get items in each order. - Join
products
table asp
to get product details. - Conditions:
- Only select orders where
o.total_amount
is greater than 500. - Ordering:
- Orders are sorted by
o.order_date
in 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_id
fromorders
whereorders.user_id
equalsusers.id
. - NOT EXISTS:
- Uses
NOT EXISTS
to find users who do not have matching records in theorders
table. - whereRaw:
- Since the
QueryBuilder
may not have awhereExists
method,whereRaw
is 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
$offset
based 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.