Use these helper functions for string operations across all databases.
Concatenate multiple strings:
use tommyknocker\pdodb\helpers\Db;
$users = $db->find()
->from('users')
->select(['full_name' => Db::concat('first_name', ' ', 'last_name')])
->get();
// SQL: CONCAT(first_name, ' ', last_name)$users = $db->find()
->from('users')
->select([
'email_with_name' => Db::concat('name', ' <', 'email', '>')
])
->get();Convert string to uppercase:
$users = $db->find()
->from('users')
->select(['name_upper' => Db::upper('name')])
->get();
// SQL: UPPER(name)Convert string to lowercase:
$users = $db->find()
->from('users')
->select(['email_lower' => Db::lower('email')])
->get();
// SQL: LOWER(email)Remove whitespace from both sides:
$users = $db->find()
->from('users')
->select(['name_trimmed' => Db::trim('name')])
->get();
// SQL: TRIM(name)Get string length:
$users = $db->find()
->from('users')
->select(['name_length' => Db::length('name')])
->get();
// SQL: LENGTH(name)Extract substring from string:
$users = $db->find()
->from('users')
->select(['first_char' => Db::substring('name', 1, 1)])
->get();
// SQL: SUBSTRING(name, 1, 1)$users = $db->find()
->from('users')
->select([
'email',
'domain' => Db::substring('email', Db::raw('LOCATE("@", email) + 1'))
])
->get();Replace text in string:
$users = $db->find()
->from('users')
->select(['clean_name' => Db::replace('name', 'Mr. ', '')])
->get();
// SQL: REPLACE(name, 'Mr. ', '')$users = $db->find()
->from('users')
->select([
'name',
'initials' => Db::concat(
Db::substring('first_name', 1, 1),
Db::substring('last_name', 1, 1)
)
])
->get();$addresses = $db->find()
->from('addresses')
->select([
'full_address' => Db::concat(
'street',
', ',
'city',
', ',
'state',
' ',
'zip'
)
])
->get();$users = $db->find()
->from('users')
->where(Db::lower('email'), Db::lower('[email protected]'), '=')
->get();
// SQL: WHERE LOWER(email) = LOWER('[email protected]')Repeat a string multiple times:
$users = $db->find()
->from('users')
->select(['banner' => Db::repeat('-', 5)])
->get();
// MySQL/PostgreSQL: REPEAT('-', 5)
// SQLite: Emulated using recursive CTEReverse a string:
$users = $db->find()
->from('users')
->select(['name_reversed' => Db::reverse('name')])
->get();
// MySQL/PostgreSQL: REVERSE(name)
// SQLite: Emulated using recursive CTEPad strings to a specific length:
$users = $db->find()
->from('users')
->select([
'left_padded' => Db::padLeft('name', 10, ' '),
'right_padded' => Db::padRight('name', 10, '.')
])
->get();
// MySQL/PostgreSQL: LPAD(name, 10, ' ') / RPAD(name, 10, '.')
// SQLite: Emulated using recursive CTE and SUBSTRMySQL:
CONCAT(first_name, ' ', last_name)PostgreSQL/SQLite:
first_name || ' ' || last_namePDOdb handles this automatically.
MySQL/PostgreSQL/MariaDB: These functions are natively supported.
SQLite: These functions are emulated using recursive CTEs:
REPEAT: Uses recursive CTE to concatenate the string multiple timesREVERSE: Uses recursive CTE to reverse characters one by oneLPAD/RPAD: Uses recursive CTE to generate padding, then concatenates and truncates
All functions work identically across all dialects.
// ✅ Clear - use concat helper
Db::concat('first_name', ' ', 'last_name')
// ❌ Less clear - avoid raw SQL for simple concatenation
Db::raw("first_name || ' ' || last_name")// For case-insensitive searches, create lowercase index
$db->connection->query('CREATE INDEX idx_users_email_lower ON users(LOWER(email))');
// Then use in queries
$users = $db->find()
->from('users')
->where(Db::lower('email'), Db::lower($email))
->get();Check if a string matches a regular expression pattern:
// Find valid email addresses
$users = $db->find()
->from('users')
->where(Db::regexpMatch('email', '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'))
->get();
// MySQL/MariaDB: (email REGEXP '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
// PostgreSQL: (email ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
// SQLite: (email REGEXP '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
// REGEXP functions are automatically registered by PDOdb (can be disabled via enable_regexp config)// Find users with phone numbers containing dashes
$users = $db->find()
->from('users')
->where(Db::regexpMatch('phone', '-'))
->get();
// With negation
$users = $db->find()
->from('users')
->where(Db::not(Db::regexpMatch('email', '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')))
->get();Replace all occurrences of a pattern with a replacement string:
// Replace dashes with spaces in phone numbers
$users = $db->find()
->from('users')
->select([
'phone',
'formatted' => Db::regexpReplace('phone', '-', ' ')
])
->get();
// MySQL/MariaDB: REGEXP_REPLACE(phone, '-', ' ')
// PostgreSQL: regexp_replace(phone, '-', ' ', 'g')
// SQLite: regexp_replace(phone, '-', ' ')
// REGEXP functions are automatically registered by PDOdb// Remove all non-alphanumeric characters
$users = $db->find()
->from('users')
->select([
'name',
'clean' => Db::regexpReplace('name', '[^a-zA-Z0-9]', '')
])
->get();Extract matched substring or capture group from a string:
// Extract domain from email
$users = $db->find()
->from('users')
->select([
'email',
'domain' => Db::regexpExtract('email', '@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})', 1)
])
->get();
// MySQL/MariaDB: REGEXP_SUBSTR(email, '@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})', 1, 1, NULL, 1)
// PostgreSQL: (regexp_match(email, '@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})'))[2]
// SQLite: regexp_extract(email, '@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})', 1)
// REGEXP functions are automatically registered by PDOdb// Extract username from email (full match)
$users = $db->find()
->from('users')
->select([
'email',
'username' => Db::regexpExtract('email', '^([a-zA-Z0-9._%+-]+)@', 1)
])
->get();// Extract area code and number from phone
$users = $db->find()
->from('users')
->select([
'phone',
'area_code' => Db::regexpExtract('phone', '\\+1-([0-9]{3})', 1),
'number' => Db::regexpExtract('phone', '\\+1-[0-9]{3}-([0-9-]+)', 1)
])
->get();MySQL/MariaDB:
REGEXPoperator for matchingREGEXP_REPLACE()function for replacementREGEXP_SUBSTR()function for extraction (MySQL 8.0+, MariaDB 10.0.5+)
PostgreSQL:
~operator for case-sensitive matching (~*for case-insensitive)regexp_replace()function for replacementregexp_match()function returns array, use array indexing for groups
SQLite:
REGEXPoperator requires extension to be loadedregexp_replace()andregexp_extract()functions require REGEXP extension- If extension is not available, operations will fail at runtime
All dialects use POSIX extended regular expressions, but there are some differences:
- MySQL/MariaDB: Uses MySQL regex syntax (similar to POSIX)
- PostgreSQL: Uses POSIX extended regex syntax
- SQLite: Uses POSIX extended regex syntax (REGEXP functions are automatically registered by PDOdb)
- MySQL/MariaDB: Group index starts at 0 (0 = full match, 1+ = capture groups)
- PostgreSQL: Array indexing starts at 1 (1 = first group)
- SQLite: Group index starts at 0 (0 = full match, 1+ = capture groups)
PDOdb handles these differences automatically.
// ✅ Use regexpMatch for validation
$validEmails = $db->find()
->from('users')
->where(Db::regexpMatch('email', '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'))
->get();// Extract components from structured strings
$users = $db->find()
->from('users')
->select([
'phone',
'country_code' => Db::regexpExtract('phone', '^\\+([0-9]+)', 1),
'area_code' => Db::regexpExtract('phone', '\\+[0-9]+-([0-9]+)', 1)
])
->get();// Normalize phone numbers by removing formatting
$users = $db->find()
->from('users')
->select([
'phone',
'normalized' => Db::regexpReplace(
Db::regexpReplace('phone', '[^0-9]', ''),
'^1',
''
)
])
->get();PDOdb automatically registers REGEXP functions (REGEXP, regexp_replace, regexp_extract) for SQLite connections using PHP's preg_* functions. This happens automatically when creating a SQLite connection.
To disable automatic REGEXP registration:
$db = new PdoDb('sqlite', [
'path' => '/path/to/database.sqlite',
'enable_regexp' => false // Disable automatic REGEXP registration
]);Note: If you disable automatic registration, you must manually register REGEXP functions or load a REGEXP extension. The automatic registration uses PHP's preg_match, preg_replace, and preg_match functions, so no external extensions are required.
- Numeric Helpers - Math operations
- Comparison Helpers - WHERE conditions
- Core Helpers - Essential helpers