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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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.