# PHP and Databases: PDO and mysqli

> PHP and databases explained: PDO and mysqli compared, prepared statements, error handling since PHP 8 and secure credentials in day-to-day operation.

Source: https://phpsos.de/en/php/php-und-datenbanken/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** PHP accesses databases through two widely used interfaces: PDO (PHP Data Objects) works with many database systems through a uniform interface, while mysqli is tailored to MySQL and MariaDB. With both, prepared statements, that is, queries with placeholders, reliably protect against SQL injection. Since PHP 8.0 (PDO) and 8.1 (mysqli) respectively, both report errors as exceptions by default.

## How PHP talks to databases

Almost every PHP application stores its content in a database: pages, orders, user accounts, settings. For this the PHP script establishes a connection, sends a query in the SQL language and processes the result. The credentials are usually in a configuration file on the same server.

This matters for management because the database usually holds the company's most valuable data. Anyone who can execute code on the web server can generally also read the configuration file and use these credentials to access the database.

## PDO and mysqli compared

| Feature | PDO | mysqli |
|---|---|---|
| Databases | many, via drivers such as `PDO_MYSQL`, `PDO_PGSQL`, `PDO_SQLITE`, `PDO_SQLSRV`, `PDO_OCI` | MySQL and MariaDB |
| Placeholders | named (`:name`) and positional (`?`) | positional (`?`) |
| Style | object-oriented | object-oriented and procedural |
| Errors as exceptions | default since PHP 8.0.0 | default since PHP 8.1.0 |
| Shorthand for queries with parameters | `prepare()` and `execute()` with an array | `execute_query()` since PHP 8.2.0 |

In practice the framework or CMS often decides. Many bring their own database layer that uses PDO or mysqli internally. Anyone taking over an application can use a simple search of the source code to find out which interface is used and where queries originate:

```bash
# Where does the application open connections, where do queries originate?
grep -rn --include='*.php' -E 'new PDO|new mysqli|mysqli_connect|->query\(' /var/www/app/src | head -50
```

Hits with `->query(` are not automatically insecure. But they deserve a second look, because values can end up directly in the SQL text there instead of being passed via placeholders. Older projects in particular mix both styles, and the check is worthwhile before any major update.

## Prepared statements: the most important pattern

With a prepared statement, the query is sent to the database with placeholders and the values are passed separately. According to the PHP manual, this protects against SQL injection if all user input runs through placeholders.

```php
<?php
declare(strict_types=1);

$pdo = new PDO(
    'mysql:host=localhost;dbname=shop;charset=utf8mb4',
    $dbUser,
    $dbPass,
    [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);

$stmt = $pdo->prepare('SELECT id, name FROM kunden WHERE plz = :plz AND aktiv = :aktiv');
$stmt->execute(['plz' => $plz, 'aktiv' => 1]);

foreach ($stmt as $zeile) {
    echo htmlspecialchars($zeile['name'], ENT_QUOTES, 'UTF-8'), "\n";
}
```

The same with mysqli in the short form available since PHP 8.2:

```php
<?php
$db = new mysqli('localhost', $dbUser, $dbPass, 'shop');
$ergebnis = $db->execute_query('SELECT id, name FROM kunden WHERE plz = ?', [$plz]);

foreach ($ergebnis as $zeile) {
    echo htmlspecialchars($zeile['name'], ENT_QUOTES, 'UTF-8'), "\n";
}
```

Developers should know two limits. Placeholders must not be placed in quotation marks; with `LIKE` searches, the wildcard characters `%` belong in the value passed. And placeholders replace only values, not table or column names. How to solve such special cases safely is shown in the guide [Preventing SQL injection in PHP](https://phpsos.de/en/ratgeber/sql-injection-php/).

## Error handling: exceptions instead of silent errors

In the past, both interfaces worked silently by default: a failed query returned `false`, and anyone who did not check for it carried on working with empty data. That has changed:

- **PDO:** Since PHP 8.0.0, `PDO::ERRMODE_EXCEPTION` is the default. Errors throw a `PDOException`.
- **mysqli:** Since PHP 8.1.0, error reporting is set to `MYSQLI_REPORT_ERROR` and `MYSQLI_REPORT_STRICT`. Errors throw a `mysqli_sql_exception`; before that, reporting was switched off.

For security this means: exceptions must be caught and logged, but must never end up in the browser with SQL text, table names or credentials. How to separate this cleanly is described on the page [Errors and exceptions in PHP](https://phpsos.de/en/php/fehler-und-exceptions/).

## Assigning credentials and privileges correctly

The database is only as well protected as its credentials. These rules have proven their worth:

1. **Separate user per application:** Never use the database administrator for the website.
2. **Only the necessary privileges:** A website usually needs to read, write, change and delete in its own database, but no privileges on other databases or server functions.
3. **No remote connection without reason:** The database should be reachable only from the web server, not from the internet.
4. **Credentials outside the web root:** Configuration and `.env` files must not be retrievable via URL. More on this in the article [Protecting credentials and .env files](https://phpsos.de/en/blog/env-dateien-schuetzen/).
5. **Encrypted connection with separate servers:** If the database is on another machine, the connection should be encrypted so that credentials and content do not travel across the network in plain text.
6. **Separate access for maintenance:** Backups, reports and administrators get their own users. That way the log shows who did what, and an account can be blocked without disturbing the website.

## Databases, PHP2Shell and the worst case

PHP2Shell does not affect the database itself but the PHP interpreter: CVE-2026-109618 and CVE-2026-109619 allow the execution of malicious code without authentication. If that succeeds, the code runs with the privileges of the PHP process and can generally read the configuration file with the database credentials. Prepared statements do not help against this, because the attack does not run through an SQL query at all.

The update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 and the restart of all PHP processes are therefore mandatory. If there are indications of a successful attack, new database credentials, a check of the database for injected content and an assessment of reporting obligations under the GDPR are part of the response. Structured [incident response](https://phpsos.de/en/leistungen/incident-response/) makes sense here.

> **Tip:**
> Change database passwords only after the server has been cleaned up. As long as malicious code is running, it can read out new credentials again.

## How does PHP SOS help?

- We check how your application accesses the database and whether users, privileges and network access meet the minimum necessary.
- We verify that configuration and credential files are outside the web root and not publicly accessible.
- After an incident we renew the credentials in the right order and check the database for injected code.
- We document the results so that data protection officers and insurers can follow them.


## Frequently asked questions

**Should I use PDO or mysqli?**  
For new projects, PDO is usually the more flexible choice, because it works with many databases through the same interface. mysqli makes sense if you use only MySQL or MariaDB and need their special features. Both are secure if prepared statements are used consistently.

**Does PDO automatically throw exceptions on errors?**  
Yes, since PHP 8.0.0 PDO::ERRMODE_EXCEPTION is the default. Before that it was the silent mode, in which errors could only be retrieved via errorCode() and errorInfo(). Older applications often still set the mode explicitly.

**Can placeholders also replace table or column names?**  
No. Placeholders stand only for values. Table and column names, for example for sorting, must be checked against a fixed list of permitted names before they are inserted into the query.

**Do I have to change the database password after PHP2Shell?**  
The update alone does not require it. But if there are indications that the server was compromised, all credentials in configuration files must be considered known. In that case you should renew them after clean-up.

## Sources

- [PHP Manual: PDO Prepared Statements](https://www.php.net/manual/en/pdo.prepared-statements.php)
- [PHP Manual: PDO Error Handling](https://www.php.net/manual/en/pdo.error-handling.php)
- [PHP Manual: mysqli::execute_query](https://www.php.net/manual/en/mysqli.execute-query.php)
- [PHP Manual: mysqli_driver::$report_mode](https://www.php.net/manual/en/mysqli-driver.report-mode.php)
