databases storage and caching
Database Security
Database security is about protecting stored data, protecting the database server, and preventing PHP code from turning user input into unsafe database actions. It is not one feature. It is a set of habits across queries, credentials, permissions, logs, backups, and operational access.
For a junior PHP developer, the most important standard is simple: do not trust input, do not leak secrets, do not give the application more database power than it needs, and do not treat backups or logs as harmless just because they are not the live database.
SQL Injection
SQL injection happens when untrusted values become part of the SQL command. Prepared statements keep values separate from SQL syntax.
<?php
declare(strict_types=1);
$safeSql = 'SELECT id, email FROM users WHERE email = :email';
$parameters = ['email' => 'sam@example.com'];
echo $safeSql . PHP_EOL;
echo json_encode($parameters, JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// SELECT id, email FROM users WHERE email = :email
// {"email":"sam@example.com"}
Prepared statements protect values. They do not protect SQL structure such as table names, column names, sort directions, or keywords. For those, use allow-lists.
<?php
declare(strict_types=1);
function safeSortClause(string $requestedSort): string
{
return match ($requestedSort) {
'email' => 'ORDER BY email ASC',
'newest' => 'ORDER BY created_at DESC',
default => 'ORDER BY id ASC',
};
}
echo safeSortClause('newest') . PHP_EOL;
echo safeSortClause('email; DROP TABLE users') . PHP_EOL;
// Prints:
// ORDER BY created_at DESC
// ORDER BY id ASC
Least Privilege
The PHP application should connect as a database user with only the permissions it needs. A normal web app account might need to read and write application tables, but it should not usually be able to create database users, drop databases, or access unrelated schemas.
Separate credentials are common for:
- The web application.
- Migrations or deployment tasks.
- Read-only reporting.
- Backups.
- Local development and tests.
If one credential leaks, least privilege limits the damage.
Secrets And Configuration
Database passwords do not belong in committed source code. Use environment-specific configuration, a secret manager, or the platform's secure configuration system.
<?php
declare(strict_types=1);
function databaseSecretStatus(array $env): array
{
return [
'database_configured' => isset($env['DB_DATABASE']),
'username_configured' => isset($env['DB_USERNAME']),
'password_configured' => isset($env['DB_PASSWORD']),
];
}
print_r(databaseSecretStatus(['DB_DATABASE' => 'app', 'DB_USERNAME' => 'app_user']));
// Prints:
// [database_configured] => 1
// [username_configured] => 1
// [password_configured] =>
Diagnostics can say whether a secret is configured. They should not print the secret.
Sensitive Data
Not all data has the same risk. Email addresses, names, addresses, tokens, password reset records, payment references, health information, and private messages may all need different handling.
Good database security includes:
- Hashing passwords with
password_hash(), never storing plain text passwords. - Avoiding storage of raw tokens where a hash or short-lived token will do.
- Minimising personal data collected.
- Redacting sensitive fields from logs.
- Applying product and legal retention rules.
<?php
declare(strict_types=1);
$passwordHash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
echo password_verify('correct horse battery staple', $passwordHash) ? 'valid' : 'invalid';
echo PHP_EOL;
// Prints:
// valid
Backups, Dumps, And Logs
Backups and database dumps often contain the same sensitive data as production. Treat them as production data unless they are properly anonymised.
Be careful with:
- Downloading production dumps to laptops.
- Sharing SQL dumps in tickets or chat.
- Logging raw queries with bound values.
- Keeping old backups forever.
- Using production personal data in development.
Backups also need restore testing. A backup that cannot be restored is not useful during an incident.
Operational Access
Direct database access should be limited and auditable. Developers may need access for debugging, support, or incidents, but that access should have clear rules.
Prefer:
- Read-only access for investigation where possible.
- Temporary elevated access rather than permanent admin access.
- Audit logs for production changes.
- Runbooks for dangerous operations.
- Peer review for manual data fixes.
What To Check
Before moving on, make sure you can:
- Use prepared statements for values.
- Use allow-lists for dynamic SQL structure.
- Explain least-privilege database users.
- Keep database credentials out of source code and logs.
- Store passwords with
password_hash(). - Treat backups, dumps, and logs as sensitive data.
- Understand why production database access needs controls.
Practice
Practice: Review A Database Security Checklist
Write a small PHP example and checklist for securing a user search feature.
Requirements
- Show SQL with a named placeholder for an email search.
- Use an allow-list for a dynamic sort option.
- Do not print or log the database password.
- Include a password hashing example.
- Include a checklist for least privilege, backups, logs, and production access.
Show solution
This solution keeps values separate from SQL and treats credentials as secrets.
<?php
declare(strict_types=1);
function userSearchSql(string $sort): array
{
$orderBy = match ($sort) {
'email' => 'ORDER BY email ASC',
'newest' => 'ORDER BY created_at DESC',
default => 'ORDER BY id ASC',
};
return [
'sql' => 'SELECT id, email FROM users WHERE email LIKE :email ' . $orderBy,
'parameters' => ['email' => '%example.com%'],
];
}
function safeDatabaseDiagnostic(array $env): array
{
return [
'database' => $env['DB_DATABASE'] ?? 'not configured',
'username_configured' => isset($env['DB_USERNAME']),
'password_configured' => isset($env['DB_PASSWORD']),
];
}
$passwordHash = password_hash('training-password', PASSWORD_DEFAULT);
print_r(userSearchSql('newest'));
print_r(safeDatabaseDiagnostic([
'DB_DATABASE' => 'app',
'DB_USERNAME' => 'app_user',
'DB_PASSWORD' => 'secret',
]));
echo password_verify('training-password', $passwordHash) ? 'password ok' : 'password invalid';
echo PHP_EOL;
// Prints:
// [sql] => SELECT id, email FROM users WHERE email LIKE :email ORDER BY created_at DESC
// [password_configured] => 1
// password ok
Checklist:
- The web app uses a least-privilege database user.
- SQL values are bound; dynamic SQL structure uses allow-lists.
- Database credentials are not committed or logged.
- Passwords are hashed with
password_hash(). - Backups and dumps are protected like production data.
- Production database access is limited, reviewed, and auditable.