All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog
and this project adheres to Semantic Versioning.
- Fixed nested where groups without other objects inside the parent group
- add some README.md improvements
openWhereGroup()andcloseWhereGroup():- Allows to create where groups. These groups can be nested and use other where and orWhere conditions inside.
- Skip decimal normalization for LOBs and non-UTF8
- Resolved PHP 8.4 deprecation warnings for implicitly nullable parameters:
get()andquery()now declare$numRowsasint|array|null(in line with the existing PHPDoc).- Optional array parameters in
rawQueryOne(),insertMulti()and the internal_buildQuery()helper are now explicitly nullable (?array).
- No behavioural changes – this release only adjusts type declarations to keep PDOdb clean under PHP 8.4.
-
whereRaw()andorWhereRaw():- Allows injecting custom WHERE fragments (including complex grouped logic, nested AND/OR blocks, function calls, multi-column comparisons, etc.).
- Supports
?placeholders plus a value array for proper PDO prepared binding, e.g.
$db->whereRaw("(base_name LIKE ? OR sku LIKE ?)", [$search, $search]); - Can be combined with existing builder methods and will be included in
$db->get(),$db->update(),$db->delete(), etc.
-
Automatic decimal comma normalization for numeric columns:
- When writing to
DECIMAL,FLOAT,DOUBLE, etc., values like"12,20"are now automatically normalized to"12.20"internally. - Removes the need to manually replace commas with dots before insert/update.
- Applies only to numeric column types; regular string/text fields are not touched.
- When writing to
- Internal WHERE builder now supports raw segments alongside the typed helpers (
whereInt(),whereStr(), …). Raw segments are appended without column/operator validation by design, but their parameters (if provided) are still bound via prepared statements.
- Decimal normalization is considered non-breaking: existing code that already passes
"12.20"continues to behave the same. The feature mainly removes repetitive pre-processing of localized numeric input.
- Reuse of named scalar placeholders across multiple SQL occurrences.
- Deterministic expansion for named arrays: :name_{occIdx}_{elemIdx}.
- Strict separation of named vs. positional placeholders.
- Clearer exceptions; guaranteed state reset on error.
- Accurate placeholder/parameter count validation for positional arrays.
- Empty arrays now throw InvalidArgumentException.
- Mixing named and positional placeholders throws InvalidArgumentException.
- Internal query counter (
_queryCounter) with automatic tracking viacountQuery() - Manual error registration via
setManualLastErrors()with_manualErrorSetflag - Batch mode flag (
_batchMode) to suppress counter increments during multi-inserts - Improved
getLastError()andgetLastErrno()to return error from latest counted query only
- Refactored
handleException()and all insert methods to unify error tracking _buildInsert()now handles counter increment internally (unless in batch mode)- Reset logic now clears
_manualErrorSetand_batchModereliably (reset(true))
- Prevented stale or incorrect query counter states after manual or multi-insert failures
paginate()now resets state cleanly and no longer double-counts queries
- Fixed
delete()query bug causingDELETE WHERE ...without table name - Resolved fatal exception when calling
where() + delete()
- Full support for
[F],[I],[N]placeholders ininsert(),update(),replace() - Subquery validation and alias extraction
- New central
_secureValidateInsertValues()for value safety
secureWhere(): stronger validation (aggregates, suspicious input, function safety)- Improved
setPageLimit(),getReturnKey()– now instance-safe - Query debug tracking (
_lastDebugQuery) activated even on errors
- Cleaned up
_buildQuery()structure and reset behavior - Improved logging via
logQuery()andlogException()in all query types
-
$db->pageLimit
→ usesetPageLimit()instead (instance-safe) -
$db->map
→ usesetReturnKey()instead -
$db->arrayBuilder(),$db->objectBuilder(),$db->jsonBuilder()
→ usesetOutputMode('array' | 'object' | 'json')instead -
$db->escape()
→ remains a non-functional dummy and may be removed in a future version -
Static global instance via
getInstance()
→ use named instances viagetInstance('your_name')instead
- Heuristic WHERE condition checker (enabled by default):
A new safeguard mechanism has been added to detect suspicious WHERE clause values based on simple heuristics.
This helps catch obvious SQL injection attempts (e.g.1; DROP TABLE,SLEEP(1), etc.) even when using the basewhere()method.
- The heuristic check can be disabled globally via:
define('PDOdb_HEURISTIC_WHERE_CHECK', false);Notes
- This check adds a small performance cost (typically 1–3 ms per affected query), but significantly improves safety for legacy or user-controlled input.
- Reorganized method order across the entire class (grouped logically)
- secureHaving(): added support for alias/aggregate validation
- _buildCondition(): detects SQL functions and skips ticks accordingly
- HAVING clauses with invalid inputs now trigger exceptions, not fatal errors
[1.3.0] – 2025-07-08
Default instance name $instance = 'default' added to simplify multi-instance management.
This ensures consistent fallback behavior if no named instance is specified.
Removed legacy methods loadData() and loadXml().
These were inherited from the original MySQLi-based implementation (ThingEngineer),
but rely on LOAD DATA INFILE and LOAD XML INFILE, which are insecure, non-portable,
and fundamentally incompatible with a PDO-based database wrapper.
Developers should use native PHP CSV/XML parsing with prepared INSERT statements instead.
- Full SQL expression validation for
groupBy()andorderBy()methods - Security filter: Blocks dangerous SQL constructs (e.g.
UNION,DROP,SLEEP, etc.) - Expression parser:
- Nested function support (e.g.
ROUND(ABS(price), 2)) - CASE WHEN validation with deep inspection
- Literal and operator detection (e.g.
total > 100)
- Nested function support (e.g.
- Extensive test coverage for allowed and blocked payloads
validateSqlExpression()is now used internally bygroupBy()andorderBy()to enforce safe syntax- Removed internal
$this->reset()call to avoid silent data loss between method calls
- Fixed missing exception in CASE WHEN expressions with invalid inner content
- Proper handling of blocked
ORDER BY RAND(),SLEEP(),DROP TABLE, and similar injection attempts
whereInt()/orWhereInt()for strict integer filteringwhereFloat()/orWhereFloat()for safe float/decimal filteringwhereString()/orWhereString()to block unsafe strings or injection attemptswhereBool()/orWhereBool()with stricttrue|false|1|0validationwhereIsNull()/orWhereIsNull()forIS NULLconditionswhereIsNotNull()/orWhereIsNotNull()forIS NOT NULLconditionswhereIn()/orWhereIn()using parameterizedIN (...)clauseswhereNotIn()/orWhereNotIn()using parameterizedNOT IN (...)clauses
- Bumped version from
v1.0.3tov1.1.0 - Updated
README.mdwith usage examples and behavior notes for all new methods