PHPackages                             knifelemon/easy-query - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. knifelemon/easy-query

ActiveLibrary[Database &amp; ORM](/categories/database)

knifelemon/easy-query
=====================

Easy-to-use PHP SQL query builder with fluent API. Designed for FlightPHP and works with any database connection.

v1.0.2.3(3mo ago)594MITPHPPHP &gt;=7.4

Since Jan 15Pushed 3mo agoCompare

[ Source](https://github.com/KnifeLemon/EasyQueryBuilder)[ Packagist](https://packagist.org/packages/knifelemon/easy-query)[ RSS](/packages/knifelemon-easy-query/feed)WikiDiscussions master Synced today

READMEChangelog (5)Dependencies (6)Versions (7)Used By (0)

EasyQuery
=========

[](#easyquery)

[![Latest Stable Version](https://camo.githubusercontent.com/d7b1e6e95ee3b6ba287e4ba190c26747d08c13e587502c9760d76c710b1f3e67/68747470733a2f2f706f7365722e707567782e6f72672f6b6e6966656c656d6f6e2f656173792d71756572792f762f737461626c65)](https://packagist.org/packages/knifelemon/easy-query)[![Total Downloads](https://camo.githubusercontent.com/41e7b1b91f571a71845cb8b2c3b4cd2cad040f93c6042f851c6c849d14a83663/68747470733a2f2f706f7365722e707567782e6f72672f6b6e6966656c656d6f6e2f656173792d71756572792f646f776e6c6f616473)](https://packagist.org/packages/knifelemon/easy-query)[![Latest Unstable Version](https://camo.githubusercontent.com/392c8920f2d3e877e314477caef0123ec6ab0808d62d990211de5b5713465f60/68747470733a2f2f706f7365722e707567782e6f72672f6b6e6966656c656d6f6e2f656173792d71756572792f762f756e737461626c65)](https://packagist.org/packages/knifelemon/easy-query)[![License](https://camo.githubusercontent.com/3456c963b1851bf0d3fe0f05330700325b7397dca537d10264ec4d787314a7d5/68747470733a2f2f706f7365722e707567782e6f72672f6b6e6966656c656d6f6e2f656173792d71756572792f6c6963656e7365)](https://packagist.org/packages/knifelemon/easy-query)[![PHP Version Require](https://camo.githubusercontent.com/486bb42fba5f5f22d8d5ec1de4d145d87c382ff5677012a17e6a446447ee5bf7/68747470733a2f2f706f7365722e707567782e6f72672f6b6e6966656c656d6f6e2f656173792d71756572792f726571756972652f706870)](https://packagist.org/packages/knifelemon/easy-query)

A lightweight, fluent PHP SQL query builder that generates SQL and parameters. Designed to work with any database connection (PDO, MySQLi, FlightPHP SimplePdo).

Features
--------

[](#features)

- 🔗 **Fluent API** - Chain methods for readable query construction
- 🛡️ **SQL Injection Protection** - Automatic parameter binding with prepared statements
- 🔧 **Raw SQL Support** - Insert raw SQL expressions with `raw()`
- 📝 **Multiple Query Types** - SELECT, INSERT, UPDATE, DELETE, COUNT
- 🔀 **JOIN Support** - INNER, LEFT, RIGHT joins with aliases
- 🎯 **Advanced Conditions** - LIKE, IN, BETWEEN, comparison operators
- 🌐 **Database Agnostic** - Returns SQL + params, use with any DB connection
- 🪶 **Lightweight** - Minimal footprint with zero required dependencies

Installation
------------

[](#installation)

### Via Composer

[](#via-composer)

```
composer require knifelemon/easy-query
```

### Manual Installation

[](#manual-installation)

Download and include the files:

```
require_once 'src/Builder.php';
require_once 'src/BuilderRaw.php';
```

Quick Start
-----------

[](#quick-start)

```
use KnifeLemon\EasyQuery\Builder;

// Simple SELECT query
$q = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->orderBy('id DESC')
    ->limit(10)
    ->build();

// Execute with PDO
$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);
$users = $stmt->fetchAll();
```

Understanding build() Return Value
----------------------------------

[](#understanding-build-return-value)

The `build()` method returns an array with two keys: `sql` and `params`. This separation is fundamental to how EasyQuery keeps your database safe.

### What You Get

[](#what-you-get)

```
$q = Builder::table('users')
    ->where(['email' => 'user@example.com'])
    ->build();

// Returns:
// [
//     'sql' => 'SELECT * FROM users WHERE email = ?',
//     'params' => ['user@example.com']
// ]
```

### Why Split SQL and Parameters?

[](#why-split-sql-and-parameters)

EasyQuery uses **prepared statements** - a security feature that prevents SQL injection attacks. Instead of inserting values directly into SQL (which is dangerous), we:

1. **Generate SQL with placeholders (`?`)** - The SQL structure is defined first
2. **Keep values separate** - User data stays in the `params` array
3. **Let the database combine them safely** - Your database driver (PDO, MySQLi) securely binds parameters

### How to Use

[](#how-to-use)

The most common pattern is:

```
// 1. Build your query
$q = Builder::table('users')
    ->where(['status' => 'active'])
    ->limit(10)
    ->build();

// 2. Prepare the SQL statement
$stmt = $pdo->prepare($q['sql']);

// 3. Execute with parameters
$stmt->execute($q['params']);

// 4. Get results
$users = $stmt->fetchAll();
```

### Why This Matters

[](#why-this-matters)

**❌ Dangerous (Never do this):**

```
// Direct concatenation = SQL injection vulnerability!
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
// If $email is: ' OR '1'='1
// SQL becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
// This returns ALL users!
```

**✅ Safe (EasyQuery way):**

```
$email = $_POST['email'];
$q = Builder::table('users')
    ->where(['email' => $email])
    ->build();
// SQL: SELECT * FROM users WHERE email = ?
// Params: ['user input']
// The database treats the input as data, not code
```

### Working with Different Frameworks

[](#working-with-different-frameworks)

EasyQuery's separation of SQL and parameters makes it compatible with any database library:

```
// PDO
$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);

// MySQLi
$stmt = $mysqli->prepare($q['sql']);
$stmt->execute($q['params']);

// FlightPHP SimplePdo
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
```

This universal approach means you can use EasyQuery with any framework or custom database setup.

Usage Examples
--------------

[](#usage-examples)

### SELECT Queries

[](#select-queries)

#### Basic SELECT

[](#basic-select)

```
$q = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->build();

// Result:
// sql: "SELECT id, name, email FROM users WHERE status = ?"
// params: ['active']
```

#### SELECT with Alias

[](#select-with-alias)

```
// Method 1: Set alias in table() method (v1.0.2.2+)
$q = Builder::table('users', 'u')
    ->select(['u.id', 'u.name'])
    ->where(['u.status' => 'active'])
    ->orderBy('u.created_at DESC')
    ->limit(10)
    ->build();

// Method 2: Set alias using alias() method
$q = Builder::table('users')
    ->alias('u')
    ->select(['u.id', 'u.name'])
    ->where(['u.status' => 'active'])
    ->orderBy('u.created_at DESC')
    ->limit(10)
    ->build();

// Result:
// sql: "SELECT u.id, u.name FROM users AS u WHERE u.status = ? ORDER BY u.created_at DESC LIMIT 10"
// params: ['active']

#### SELECT with JOIN

```php
$q = Builder::table('users')
    ->alias('u')
    ->select(['u.id', 'u.name', 'p.title', 'p.content'])
    ->innerJoin('posts', 'u.id = p.user_id', 'p')
    ->where(['u.status' => 'active'])
    ->orderBy('p.published_at DESC')
    ->build();

// Result:
// sql: "SELECT u.id, u.name, p.title, p.content FROM users AS u INNER JOIN posts AS p ON u.id = p.user_id WHERE u.status = ? ORDER BY p.published_at DESC"
// params: ['active']

### WHERE Conditions

#### Simple Equality

```php
$q = Builder::table('users')
    ->where(['id' => 123, 'status' => 'active'])
    ->build();
// WHERE id = ? AND status = ?

```

#### Comparison Operators

[](#comparison-operators)

```
$q = Builder::table('users')
    ->where([
        'age' => ['>=', 18],
        'score' => ['', Builder::raw('(SELECT AVG(price) FROM products)')]
    ])
    ->build();

// Result:
// sql: "SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products)"
// params: []
```

#### Raw SQL with Bindings

[](#raw-sql-with-bindings)

When you need parameterized values in raw expressions:

```
// Raw expression with bound parameters
$q = Builder::table('orders')
    ->update([
        'total' => Builder::raw('COALESCE(subtotal, ?) + ?', [0, 10])
    ])
    ->where(['id' => 1])
    ->build();

// Result:
// sql: "UPDATE orders SET total = COALESCE(subtotal, ?) + ? WHERE id = ?"
// params: [0, 10, 1]
```

#### Safe Identifiers for User Input

[](#safe-identifiers-for-user-input)

When column names come from user input (e.g., dynamic sorting), use `safeIdentifier()` to prevent SQL injection:

```
// Validate user-provided column name
$sortColumn = $_GET['sort'];  // e.g., 'created_at'
$safeColumn = Builder::safeIdentifier($sortColumn);

$q = Builder::table('users')
    ->orderBy($safeColumn . ' DESC')
    ->build();

// If user tries: "name; DROP TABLE users--"
// Throws InvalidArgumentException: Invalid identifier
```

#### Safe Raw Expressions with User Input

[](#safe-raw-expressions-with-user-input)

Use `rawSafe()` when building raw SQL with user-provided column names:

```
// User selects which column to aggregate
$userColumn = $_GET['aggregate_column'];  // e.g., 'total_amount'

$q = Builder::table('orders')
    ->select([
        Builder::rawSafe('COALESCE(SUM({col}), ?)', ['col' => $userColumn], [0])->value . ' AS total'
    ])
    ->build();

// Result (with safe column):
// sql: "SELECT COALESCE(SUM(total_amount), ?) AS total FROM orders"
// params: [0]

// If user tries SQL injection, throws InvalidArgumentException
```

```
// Multiple safe identifiers
use KnifeLemon\EasyQuery\BuilderRaw;

$raw = BuilderRaw::withIdentifiers(
    '{table}.{col1} + {table}.{col2}',
    ['table' => 'orders', 'col1' => 'price', 'col2' => 'tax']
);
// Result: "orders.price + orders.tax"
```

Framework Integration
---------------------

[](#framework-integration)

### FlightPHP Integration

[](#flightphp-integration)

EasyQuery works with [FlightPHP](https://flightphp.com/)'s SimplePdo by generating SQL and parameters that you pass directly to SimplePdo methods.

```
use KnifeLemon\EasyQuery\Builder;

// Register SimplePdo with FlightPHP
Flight::register('db', \flight\database\SimplePdo::class, [
    'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
    'username',
    'password',
    [
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
]);

// In your FlightPHP route
Flight::route('GET /users', function() {
    $q = Builder::table('users')
        ->select(['id', 'name', 'email'])
        ->where(['status' => 'active'])
        ->orderBy('created_at DESC')
        ->limit(20)
        ->build();

    // SimplePdo returns Collection objects
    $users = Flight::db()->fetchAll($q['sql'], $q['params']);

    // Collection objects have getData() method that returns array
    $usersArray = array_map(fn($user) => $user->getData(), $users);

    Flight::json(['users' => $usersArray]);
});

// Using fetchField for COUNT queries (returns single value)
Flight::route('GET /users/count', function() {
    $q = Builder::table('users')
        ->count()
        ->where(['status' => 'active'])
        ->build();

    $count = Flight::db()->fetchField($q['sql'], $q['params']);

    Flight::json(['count' => (int)$count]);
});

// INSERT with FlightPHP
Flight::route('POST /users', function() {
    $data = Flight::request()->data;

    $q = Builder::table('users')
        ->insert([
            'name' => $data->name,
            'email' => $data->email,
            'created_at' => Builder::raw('NOW()')
        ])
        ->build();

    Flight::db()->runQuery($q['sql'], $q['params']);
    $userId = Flight::db()->lastInsertId();

    Flight::json(['success' => true, 'id' => $userId]);
});
```

### Legacy PHP / PDO Integration

[](#legacy-php--pdo-integration)

```
use KnifeLemon\EasyQuery\Builder;

// PDO connection
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// SELECT with PDO
$q = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->build();

$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// INSERT with PDO
$q = Builder::table('users')
    ->insert([
        'name' => 'Jane Doe',
        'email' => 'jane@example.com'
    ])
    ->build();

$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);
$userId = $pdo->lastInsertId();

// UPDATE with PDO
$q = Builder::table('users')
    ->update(['status' => 'inactive'])
    ->where(['id' => $userId])
    ->build();

$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);
$affectedRows = $stmt->rowCount();
```

### MySQLi Integration

[](#mysqli-integration)

```
use KnifeLemon\EasyQuery\Builder;

$mysqli = new mysqli('localhost', 'user', 'pass', 'mydb');

$q = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->build();

// Prepare statement
$stmt = $mysqli->prepare($q['sql']);

// Bind parameters dynamically
$types = str_repeat('s', count($q['params'])); // 's' for string, adjust as needed
$stmt->bind_param($types, ...$q['params']);

$stmt->execute();
$result = $stmt->get_result();
$users = $result->fetch_all(MYSQLI_ASSOC);
```

API Reference
-------------

[](#api-reference)

### Static Methods

[](#static-methods)

#### `Builder::table(string $table, string $alias = ''): Builder`

[](#buildertablestring-table-string-alias---builder)

Set the table name for the query. Optionally set a table alias in the same call.

#### `Builder::raw(string $value): BuilderRaw`

[](#builderrawstring-value-builderraw)

Create a raw SQL expression that will be inserted directly without parameter binding.

#### `Builder::raw(string $value, array $bindings = []): BuilderRaw`

[](#builderrawstring-value-array-bindings---builderraw)

Create a raw SQL expression with optional bound parameters for `?` placeholders.

#### `Builder::rawSafe(string $expression, array $identifiers, array $bindings = []): BuilderRaw`

[](#builderrawsafestring-expression-array-identifiers-array-bindings---builderraw)

Create a raw SQL expression with safe identifier substitution. Use `{placeholder}` syntax for identifiers.

#### `Builder::safeIdentifier(string $identifier): string`

[](#buildersafeidentifierstring-identifier-string)

Validate and return a safe column/table identifier. Only allows alphanumeric, underscores, and dots.

### Instance Methods

[](#instance-methods)

#### `alias(string $alias): self`

[](#aliasstring-alias-self)

Set an alias for the table.

#### `select(string|array $columns = '*'): self`

[](#selectstringarray-columns---self)

Set the columns to select.

#### `where(array $conditions): self`

[](#wherearray-conditions-self)

Add WHERE conditions. Multiple calls are combined with AND.

#### `orWhere(array $conditions): self`

[](#orwherearray-conditions-self)

Add OR WHERE conditions.

#### `join(string $table, string $condition, string $alias = '', string $type = 'INNER'): self`

[](#joinstring-table-string-condition-string-alias---string-type--inner-self)

Add a JOIN clause.

#### `leftJoin(string $table, string $condition, string $alias = ''): self`

[](#leftjoinstring-table-string-condition-string-alias---self)

Add a LEFT JOIN clause.

#### `innerJoin(string $table, string $condition, string $alias = ''): self`

[](#innerjoinstring-table-string-condition-string-alias---self)

Add an INNER JOIN clause.

#### `groupBy(string $groupBy): self`

[](#groupbystring-groupby-self)

Add GROUP BY clause.

#### `orderBy(string $orderBy): self`

[](#orderbystring-orderby-self)

Add ORDER BY clause.

#### `limit(int $limit, int $offset = 0): self`

[](#limitint-limit-int-offset--0-self)

Add LIMIT and optional OFFSET.

#### `count(string $column = '*'): self`

[](#countstring-column---self)

Set the query action to COUNT.

#### `insert(array $data): self`

[](#insertarray-data-self)

Set the query action to INSERT with data.

#### `update(array $data): self`

[](#updatearray-data-self)

Set the query action to UPDATE with data.

#### `delete(): self`

[](#delete-self)

Set the query action to DELETE.

#### `clearWhere(): self`

[](#clearwhere-self)

Clear WHERE conditions and parameters (allows query builder reuse).

#### `clearSelect(): self`

[](#clearselect-self)

Clear SELECT columns (reset to default '\*').

#### `clearJoin(): self`

[](#clearjoin-self)

Clear all JOIN clauses.

#### `clearGroupBy(): self`

[](#cleargroupby-self)

Clear GROUP BY clause.

#### `clearOrderBy(): self`

[](#clearorderby-self)

Clear ORDER BY clause.

#### `clearLimit(): self`

[](#clearlimit-self)

Clear LIMIT and OFFSET.

#### `clearAll(): self`

[](#clearall-self)

Clear all query conditions (reset builder to initial state).

#### `build(): array`

[](#build-array)

Build and return the query as `['sql' => string, 'params' => array]`.

#### `get(): array`

[](#get-array)

Alias for `build()`.

#### `buildSQL(): string`

[](#buildsql-string)

Build and return only the SQL string (for SELECT queries).

#### `getParams(): array`

[](#getparams-array)

Get the parameter array for binding.

Advanced Examples
-----------------

[](#advanced-examples)

### Complex JOIN with Multiple Conditions

[](#complex-join-with-multiple-conditions)

```
$q = Builder::table('orders')
    ->alias('o')
    ->select([
        'o.id',
        'o.total',
        'u.name AS customer_name',
        'p.title AS product_title'
    ])
    ->innerJoin('users', 'o.user_id = u.id', 'u')
    ->leftJoin('order_items', 'o.id = oi.order_id', 'oi')
    ->leftJoin('products', 'oi.product_id = p.id', 'p')
    ->where([
        'o.status' => 'completed',
        'o.total' => ['>=', 100],
        'o.created_at' => ['>=', '2024-01-01']
    ])
    ->groupBy('o.id')
    ->orderBy('o.created_at DESC')
    ->limit(50)
    ->build();
```

### Dynamic Query Building

[](#dynamic-query-building)

```
$query = Builder::table('products')->alias('p');

// Conditionally add conditions
if (!empty($categoryId)) {
    $query->where(['p.category_id' => $categoryId]);
}

if (!empty($minPrice)) {
    $query->where(['p.price' => ['>=', $minPrice]]);
}

if (!empty($searchTerm)) {
    $query->where(['p.name' => ['LIKE', "%{$searchTerm}%"]]);
}

// Add sorting
$query->orderBy('p.created_at DESC')->limit(20);

$result = $query->build();
```

### Query Builder Reuse

[](#query-builder-reuse)

The query builder can be reused by clearing specific conditions or resetting entirely. This is useful when you need to execute similar queries with different parameters.

```
// Create a base query
$baseQuery = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->orderBy('created_at DESC');

// First query: Active users in the last 30 days
$q1 = $baseQuery
    ->where(['created_at' => ['>=', date('Y-m-d', strtotime('-30 days'))]])
    ->limit(10)
    ->build();

$recentUsers = executeQuery($q1);

// Clear WHERE to reuse the builder
$baseQuery->clearWhere();

// Second query: All active premium users
$q2 = $baseQuery
    ->where(['status' => 'active', 'plan' => 'premium'])
    ->limit(20)
    ->build();

$premiumUsers = executeQuery($q2);

// Clear specific parts
$baseQuery
    ->clearSelect()
    ->clearOrderBy()
    ->clearLimit();

// Third query: Count active users
$q3 = $baseQuery
    ->count()
    ->where(['status' => 'active'])
    ->build();

$activeCount = executeQuery($q3);
```

#### Clear Methods Usage

[](#clear-methods-usage)

```
// Clear only WHERE conditions
$query->clearWhere();

// Clear only SELECT columns
$query->clearSelect();

// Clear only JOINs
$query->clearJoin();

// Clear only ORDER BY
$query->clearOrderBy();

// Clear only GROUP BY
$query->clearGroupBy();

// Clear only LIMIT and OFFSET
$query->clearLimit();

// Clear everything and start fresh
$query->clearAll();
```

#### Practical Example: Pagination with Reuse

[](#practical-example-pagination-with-reuse)

```
// Base query for user list
$usersQuery = Builder::table('users')
    ->select(['id', 'name', 'email', 'created_at'])
    ->where(['status' => 'active'])
    ->orderBy('created_at DESC');

// Get total count
$countQuery = clone $usersQuery;
$countResult = $countQuery
    ->clearSelect()
    ->count()
    ->build();

$totalUsers = executeQuery($countResult)[0]['cnt'];

// Get paginated results
$page = 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;

$listResult = $usersQuery
    ->limit($perPage, $offset)
    ->build();

$users = executeQuery($listResult);

// Next page - reuse the same query
$usersQuery->clearLimit();
$page = 2;
$offset = ($page - 1) * $perPage;

$nextPageResult = $usersQuery
    ->limit($perPage, $offset)
    ->build();

$nextPageUsers = executeQuery($nextPageResult);
```

### Batch Insert Helper

[](#batch-insert-helper)

```
function batchInsert($pdo, $table, array $rows) {
    $pdo->beginTransaction();
    try {
        foreach ($rows as $row) {
            $q = Builder::table($table)->insert($row)->build();
            $stmt = $pdo->prepare($q['sql']);
            $stmt->execute($q['params']);
        }
        $pdo->commit();
        return true;
    } catch (Exception $e) {
        $pdo->rollBack();
        throw $e;
    }
}

// Usage
$users = [
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob', 'email' => 'bob@example.com'],
    ['name' => 'Charlie', 'email' => 'charlie@example.com']
];

batchInsert($pdo, 'users', $users);
```

Security
--------

[](#security)

This library uses **prepared statements with parameter binding** to protect against SQL injection attacks. Parameters are never directly concatenated into SQL strings.

### Safe Parameter Binding

[](#safe-parameter-binding)

```
// ✅ SAFE - Using parameter binding
$q = Builder::table('users')
    ->where(['email' => $_POST['email']])
    ->build();

// ✅ SAFE - Using raw() with SQL functions
$q = Builder::table('users')
    ->update(['updated_at' => Builder::raw('NOW()')])
    ->build();

// ❌ DANGEROUS - Never do this!
$q = Builder::table('users')
    ->where(['email' => Builder::raw("'{$_POST['email']}'")])  // SQL injection risk!
    ->build();
```

### Safe Column Names from User Input

[](#safe-column-names-from-user-input)

When column names come from user input (e.g., sorting, aggregation), use these safety methods:

```
// ✅ SAFE - Validate column name
$sortColumn = Builder::safeIdentifier($_GET['sort']);
$q = Builder::table('users')->orderBy($sortColumn . ' DESC')->build();

// ✅ SAFE - Safe raw expression with user column
$q = Builder::table('orders')
    ->select([Builder::rawSafe('SUM({col})', ['col' => $_GET['column']])->value])
    ->build();

// ❌ DANGEROUS - Never concatenate user input in raw()
$q = Builder::table('orders')
    ->select([Builder::raw("SUM({$_GET['column']})")])
    ->build();
```

### Allowed Identifier Characters

[](#allowed-identifier-characters)

`safeIdentifier()` and `rawSafe()` only allow:

- Letters (a-z, A-Z)
- Numbers (0-9)
- Underscores (\_)
- Dots (.) for table.column notation

Any other characters will throw an `InvalidArgumentException`.

Debugging with Tracy
--------------------

[](#debugging-with-tracy)

EasyQuery provides automatic Tracy Debugger integration with a beautiful custom panel. **No setup required!** Just install Tracy and use EasyQuery - the debug panel will automatically appear.

### Automatic Setup

[](#automatic-setup)

```
use Tracy\Debugger;
use KnifeLemon\EasyQuery\Builder;

// Enable Tracy (development only)
Debugger::enable();

// That's it! Just use EasyQuery normally
$q = Builder::table('users')
    ->select(['id', 'name', 'email'])
    ->where(['status' => 'active'])
    ->orderBy('created_at DESC')
    ->limit(10)
    ->build();

// All queries are automatically logged to Tracy panel
// No manual initialization needed!
```

### How It Works

[](#how-it-works)

- **Auto-initialization**: First `Builder` instantiation automatically initializes Tracy logging
- **Zero configuration**: Just have Tracy installed and it works
- **Automatic logging**: Every `build()` call is logged to the custom Tracy panel

### Tracy Panel Features

[](#tracy-panel-features)

The custom Tracy panel shows:

- **Summary Cards**: Total queries, breakdown by type (SELECT, INSERT, UPDATE, DELETE, COUNT)
- **Query List**: Each query with:
    - Action type badge (color-coded)
    - Generated SQL (syntax highlighted)
    - Parameters array
    - Expandable details (table, where, joins, order, limit, etc.)
    - Timestamp

**Install Tracy:**

```
composer require tracy/tracy
```

If Tracy is not installed, EasyQuery works normally without any debug output.

Testing
-------

[](#testing)

```
# Run tests
composer test

# Run tests with coverage
composer test-coverage

# Run static analysis
composer phpstan
```

Requirements
------------

[](#requirements)

- PHP &gt;= 7.4
- PDO or MySQLi extension (for database connectivity)

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

License
-------

[](#license)

This project is licensed under the MIT License - see the LICENSE file for details.

Credits
-------

[](#credits)

Created and maintained by [KnifeLemon](https://github.com/knifelemon)

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md) for version history.

Support
-------

[](#support)

If you encounter any issues or have questions, please [open an issue](https://github.com/knifelemon/EasyQueryBuilder/issues) on GitHub.

Resources
---------

[](#resources)

- [FlightPHP Framework](https://flightphp.com/)
- [FlightPHP GitHub](https://github.com/flightphp/core)
- [FlightPHP SimplePdo Documentation](https://docs.flightphp.com/learn/simple-pdo)

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance79

Regular maintenance activity

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Total

6

Last Release

109d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8b444ab99e0d4b7938dc77346d6783a924ca8c91139855645f4b2866c2beff85?d=identicon)[KnifeLemon](/maintainers/KnifeLemon)

---

Top Contributors

[![KnifeLemon](https://avatars.githubusercontent.com/u/13937016?v=4)](https://github.com/KnifeLemon "KnifeLemon (9 commits)")

---

Tags

phpdatabasesqlpdomysqliquery builderfluent-apiflightphp

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/knifelemon-easy-query/health.svg)

```
[![Health](https://phpackages.com/badges/knifelemon-easy-query/health.svg)](https://phpackages.com/packages/knifelemon-easy-query)
```

###  Alternatives

[clouddueling/mysqldump-php

PHP version of mysqldump cli that comes with MySQL

1.3k23.2k](/packages/clouddueling-mysqldump-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
