PHPackages                             dzentota/secure-db - 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. [Security](/categories/security)
4. /
5. dzentota/secure-db

ActiveLibrary[Security](/categories/security)

dzentota/secure-db
==================

A secure-by-default PDO wrapper that prevents SQL injection and provides a developer-friendly API

07PHP

Since Mar 25Pushed 1mo agoCompare

[ Source](https://github.com/dzentota/secure-db)[ Packagist](https://packagist.org/packages/dzentota/secure-db)[ RSS](/packages/dzentota-secure-db/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Secure PDO Wrapper
==================

[](#secure-pdo-wrapper)

A secure-by-default PDO wrapper that prevents SQL injection vulnerabilities and provides a developer-friendly API for database operations.

Features
--------

[](#features)

### 🔒 Security First

[](#-security-first)

- **Prepared statements by default** - All queries use prepared statements, making SQL injection nearly impossible
- **TypedValue integration** - Seamless integration with value objects for robust input validation
- **Identifier quoting** - Safe handling of dynamic table and column names
- **Comprehensive audit logging** - Track all database operations for security review

### 🚀 Developer Friendly

[](#-developer-friendly)

- **Intuitive API** - Simple, consistent methods for common database operations
- **Special placeholders** - Advanced placeholder system for complex queries
- **Transaction management** - Both explicit and automatic transaction handling
- **Error handling** - Comprehensive error management with customizable handlers

### 🎯 Advanced Features

[](#-advanced-features)

- **Dynamic query building** - Build complex queries safely with special placeholders
- **Multi-database support** - Works with MySQL, PostgreSQL, SQLite, and more
- **Connection management** - Factory-based connection creation and existing PDO wrapping
- **Prefix support** - Table name prefixing for shared databases

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

[](#installation)

```
composer require dzentota/secure-db
```

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

[](#quick-start)

```
use SecureDb\Db;

// Create a new connection
$db = Db::connect('mysql:host=localhost;dbname=mydb', 'username', 'password');

// Or wrap an existing PDO instance
$pdo = new PDO('sqlite::memory:');
$db = Db::wrap($pdo, 'sqlite');

// Basic queries
$users = $db->select('SELECT * FROM users WHERE active = ?', 1);
$user = $db->selectRow('SELECT * FROM users WHERE id = ?', 123);
$userCount = $db->selectCell('SELECT COUNT(*) FROM users');

// CRUD operations
$userId = $db->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
$affected = $db->update('users', ['active' => 0], ['id' => $userId]);
$deleted = $db->delete('users', ['id' => $userId]);
```

Core API
--------

[](#core-api)

### Connection Management

[](#connection-management)

```
// Factory connection
$db = Db::connect(string $dsn, string $username = '', string $password = '', array $options = []);

// Wrap existing PDO
$db = Db::wrap(PDO $pdo, string $driverName);
```

### Query Execution

[](#query-execution)

```
// Fetch all rows
$users = $db->select('SELECT * FROM users WHERE active = ?', 1);

// Fetch single row
$user = $db->selectRow('SELECT * FROM users WHERE id = ?', 123);

// Fetch single column from all rows
$names = $db->selectCol('SELECT name FROM users WHERE active = ?', 1);

// Fetch single cell value
$count = $db->selectCell('SELECT COUNT(*) FROM users WHERE active = ?', 1);

// Execute non-SELECT query
$affected = $db->query('UPDATE users SET last_login = NOW() WHERE id = ?', 123);

// Paginated results
$totalRows = 0;
$users = $db->selectPage($totalRows, 'SELECT * FROM users WHERE active = ?', 1);
```

### CRUD Operations

[](#crud-operations)

```
// Insert
$userId = $db->insert('users', [
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'active' => 1
]);

// Update
$affected = $db->update('users',
    ['name' => 'Jane Doe', 'active' => 0],
    ['id' => $userId]
);

// Delete
$deleted = $db->delete('users', ['id' => $userId]);
```

### Transaction Management

[](#transaction-management)

```
// Explicit transactions
$db->transaction();
try {
    $db->insert('orders', $orderData);
    $db->insert('order_items', $itemData);
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

// Automatic transaction wrapper
$result = $db->tryFlatTransaction(function($db) {
    $db->insert('orders', $orderData);
    $db->insert('order_items', $itemData);
    return 'success';
});
```

Advanced Features
-----------------

[](#advanced-features)

### Special Placeholders

[](#special-placeholders)

#### Array Placeholder (`?a`)

[](#array-placeholder-a)

```
// IN clause with array
$users = $db->select('SELECT * FROM users WHERE id IN(?a)', [1, 2, 3]);
// Generates: SELECT * FROM users WHERE id IN(?, ?, ?)

// SET clause with associative array
$affected = $db->query('UPDATE users SET ?a WHERE id = ?',
    ['name' => 'John', 'email' => 'john@example.com'],
    123
);
// Generates: UPDATE users SET `name` = ?, `email` = ? WHERE id = ?

```

#### Identifier Placeholder (`?#`)

[](#identifier-placeholder-)

```
// Dynamic table/column names
$users = $db->select('SELECT * FROM ?# WHERE ?# = ?', 'users', 'active', 1);
// Generates: SELECT * FROM `users` WHERE `active` = ?

// With qualified identifiers
$data = $db->select('SELECT ?#.* FROM ?#', 'u.name', 'users u');
// Generates: SELECT `u`.`name` FROM `users` `u`
```

#### Prefix Placeholder (`?_`)

[](#prefix-placeholder-_)

```
// Table name prefixing
$db->setIdentifierPrefix('app_');
$users = $db->select('SELECT * FROM ?_users WHERE ?_users.active = ?', 1);
// Generates: SELECT * FROM `app_users` WHERE `app_users`.`active` = ?

```

### Macro Substitution (Conditional SQL Blocks)

[](#macro-substitution-conditional-sql-blocks)

Build dynamic queries with conditional SQL blocks using `MacroControl::SKIP`:

```
use SecureDb\MacroControl;

// Conditional WHERE clause
$includeActiveFilter = true;
$users = $db->select(
    'SELECT * FROM users WHERE id > ? { AND active = ? } ORDER BY id',
    1,
    $includeActiveFilter ? 1 : MacroControl::SKIP
);
// With filter: SELECT * FROM users WHERE id > ? AND active = ? ORDER BY id
// Without filter: SELECT * FROM users WHERE id > ? ORDER BY id

// Multiple conditional blocks
$includeNameFilter = true;
$includeAgeFilter = false;
$users = $db->select(
    'SELECT * FROM users WHERE 1=1 { AND name LIKE ? } { AND age > ? }',
    $includeNameFilter ? 'John%' : MacroControl::SKIP,
    $includeAgeFilter ? 25 : MacroControl::SKIP
);
// Generates: SELECT * FROM users WHERE 1=1 AND name LIKE ?

// Works with array placeholders
$includeIdFilter = true;
$users = $db->select(
    'SELECT * FROM users WHERE active = ? { AND id IN(?a) }',
    1,
    $includeIdFilter ? [1, 2, 3] : MacroControl::SKIP
);
// Generates: SELECT * FROM users WHERE active = ? AND id IN(?, ?, ?)

// Conditional UPDATE clauses
$updateAge = false;
$affected = $db->query(
    'UPDATE users SET name = ? { , age = ? } WHERE id = ?',
    'John Doe',
    $updateAge ? 30 : MacroControl::SKIP,
    123
);
// Generates: UPDATE users SET name = ? WHERE id = ?

```

### Error Handling and Logging

[](#error-handling-and-logging)

```
// Custom error handler
$db->setErrorHandler(function($error, $query, $params) {
    // Log error, send notifications, etc.
    error_log("Database error: " . $error->getMessage());
});

// Query logging
$db->setLogger(function($logData) {
    // Log all queries for debugging/auditing
    error_log(json_encode([
        'query' => $logData['query'],
        'execution_time' => $logData['execution_time'],
        'caller' => $logData['caller']
    ]));
});

// Enable parameter logging (opt-in — disabled by default to protect sensitive data)
$db->setLogQueryParams(true);

// Strict mode (default: true) — when false, returns safe defaults instead of throwing exceptions
$db->setStrictMode(false); // select → [], selectRow/selectCell → null, query/update/delete/insert → 0
```

### TypedValue Integration

[](#typedvalue-integration)

```
use SomeNamespace\TypedValue;

// Automatic extraction of TypedValue objects
$email = new EmailValue('john@example.com');
$age = new AgeValue(25);

$userId = $db->insert('users', [
    'email' => $email,    // Automatically calls $email->toNative()
    'age' => $age,        // Automatically calls $age->toNative()
    'name' => 'John Doe'  // Regular value passed as-is
]);
```

Database Support
----------------

[](#database-support)

DatabaseIdentifier QuotingStatusMySQLBackticks (`)✅ Full SupportPostgreSQLDouble quotes (")✅ Full SupportSQLiteDouble quotes (")✅ Full SupportSQL ServerBrackets (\[\])✅ Basic SupportOracleDouble quotes (")✅ Basic SupportSecurity Features
-----------------

[](#security-features)

### SQL Injection Prevention

[](#sql-injection-prevention)

```
// ✅ SAFE - Uses prepared statements
$users = $db->select('SELECT * FROM users WHERE name = ?', $_POST['name']);

// ✅ SAFE - Identifier quoting
$users = $db->select('SELECT * FROM ?# WHERE ?# = ?', $_POST['table'], $_POST['column'], $_POST['value']);

// ✅ SAFE - Array placeholder
$users = $db->select('SELECT * FROM users WHERE id IN(?a)', $_POST['ids']);

// ❌ IMPOSSIBLE - No string concatenation methods provided
// $users = $db->select('SELECT * FROM users WHERE name = ' . $_POST['name']);
```

### Input Validation

[](#input-validation)

```
// Automatic TypedValue handling
$email = new EmailValue($_POST['email']); // Validates email format
$age = new AgeValue($_POST['age']);       // Validates age range

$userId = $db->insert('users', [
    'email' => $email,  // Extracted safely with toNative()
    'age' => $age,      // Extracted safely with toNative()
]);
```

### Audit Logging

[](#audit-logging)

Query parameters are **excluded from logs by default** to prevent sensitive data (passwords, tokens, PII) from leaking into log files. Enable them explicitly only for debugging in non-production environments.

```
$db->setLogger(function($logData) {
    // Log to security audit system — params are NOT included unless setLogQueryParams(true)
    SecurityAudit::log([
        'query' => $logData['query'],
        'user_id' => getCurrentUserId(),
        'timestamp' => $logData['timestamp'],
        'caller' => $logData['caller']
    ]);
});

// Opt-in to logging parameters (e.g. in a development environment only)
if ($isDev) {
    $db->setLogQueryParams(true);
}
```

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run with coverage:

```
composer test -- --coverage-html coverage
```

Run static analysis:

```
composer psalm
composer phpstan
```

Development
-----------

[](#development)

### Requirements

[](#requirements)

- PHP 8.1 or higher
- PDO extension
- Composer

### Setup

[](#setup)

```
git clone https://github.com/dzentota/secure-db.git
cd secure-db
composer install
composer test
```

### Contributing

[](#contributing)

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Run the test suite
6. Submit a pull request

License
-------

[](#license)

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Security
--------

[](#security)

If you discover any security vulnerabilities, please send an email to  instead of using the issue tracker.

Changelog
---------

[](#changelog)

### 1.2.0

[](#120)

- **Security:** `connect()` no longer includes PDO error details (DSN, credentials) in the exception message
- **Security:** Query parameters are excluded from logs by default; opt-in via `setLogQueryParams(true)`
- **Security:** `getCallerInfo()` now logs only the filename, not the full server filesystem path
- **Fix:** `setStrictMode(false)` now works as documented — query methods return safe defaults (`[]`, `null`, `0`) instead of throwing on error
- **Fix:** `insert()`, `update()`, `delete()` now throw immediately on an empty table name
- **Fix:** SQLite identifier quoting unified to ANSI double-quotes (consistent with all other drivers); removed incomplete reserved-word detection
- **Fix:** Removed duplicate pre-execution log entry — queries are now logged once, after execution, with accurate timing
- `logError()` now includes `severity` and `error_code` fields
- `SecureDbException` constructor now accepts `?\Throwable` as the previous exception (was `?Exception`)

### 1.1.0

[](#110)

- Added macro substitution for conditional SQL blocks using `{ }` syntax
- Improved SQLite identifier quoting with smart detection
- Enhanced placeholder processing to handle mixed placeholder types correctly
- Added comprehensive macro substitution examples

### 1.0.0

[](#100)

- Initial release
- Core secure PDO wrapper functionality
- Special placeholder support
- Transaction management
- Comprehensive test suite

###  Health Score

21

—

LowBetter than 19% of packages

Maintenance60

Regular maintenance activity

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/a3d7176a1dd4827823dd352f15bd833425a8d47a719560a1bded654d832ed060?d=identicon)[dzentota](/maintainers/dzentota)

---

Top Contributors

[![dzentota](https://avatars.githubusercontent.com/u/834595?v=4)](https://github.com/dzentota "dzentota (3 commits)")

---

Tags

pdopdo-wrappersecurity

### Embed Badge

![Health badge](/badges/dzentota-secure-db/health.svg)

```
[![Health](https://phpackages.com/badges/dzentota-secure-db/health.svg)](https://phpackages.com/packages/dzentota-secure-db)
```

###  Alternatives

[defuse/php-encryption

Secure PHP Encryption Library

3.9k162.4M214](/packages/defuse-php-encryption)[roave/security-advisories

Prevents installation of composer packages with known security vulnerabilities: no API, simply require it

2.9k97.3M6.4k](/packages/roave-security-advisories)[mews/purifier

Laravel 5/6/7/8/9/10 HtmlPurifier Package

2.0k16.7M113](/packages/mews-purifier)[robrichards/xmlseclibs

A PHP library for XML Security

41278.1M118](/packages/robrichards-xmlseclibs)[bjeavons/zxcvbn-php

Realistic password strength estimation PHP library based on Zxcvbn JS

86917.5M63](/packages/bjeavons-zxcvbn-php)[enlightn/security-checker

A PHP dependency vulnerabilities scanner based on the Security Advisories Database.

33732.2M110](/packages/enlightn-security-checker)

PHPackages © 2026

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