Home / Articles / Web Development
Web Development

When Data Doesn't Appear: PHP and MySQL Debugging Methods Without Guessing

Website errors often feel confusing because the symptoms appear in one place, while the cause is located elsewhere. With a structured debugging flow, PHP, API, and MySQL issues can be narrowed down more effectively.

Saat Data Tak Muncul: Metode Debugging PHP dan MySQL Tanpa Menebak-nebak

When a website page displays empty data, shows a 500 error, or the save button seems unresponsive, the most common reaction is to immediately change several parts of the code at once. The problem is, this approach often leads us to lose clues. Afterward, we no longer know which changes fixed the issue and which ones introduced new bugs.

Good debugging is not just about finding the wrong line of code. Debugging is the process of proving where the data flow stops: whether the input is not received, PHP fails to process, the MySQL query is incorrect, or the API response is not handled properly by JavaScript.

Start from symptoms, not assumptions

The first step is to write down the symptoms specifically. "The website is broken" is too broad to serve as a clue. Change it to a testable description, for example: "the form was successfully submitted, but the user_id field is saved as NULL" or "the endpoint returns a status 200, but the items array is empty."

Also separate the following three aspects:

  • Input: data sent by users or other applications.
  • Process: validation, PHP logic, and database queries.
  • Output: data that is stored, JSON responses, or displays in the browser.

This separation helps us test the system step by step. If the input is already wrong, there’s no point in optimizing the query right away. Conversely, if the query proves correct but the page remains empty, attention can shift to JavaScript or the response format.

Check the input actually received by PHP

The values visible in the form may not be the same as those received by the server. Incorrect name attribute names, different request methods, or undecoded JSON data can cause PHP variables to contain empty values.

For local testing, log the request contents in a controlled manner:

<?php
error_log(json_encode([
    'method' => $_SERVER['REQUEST_METHOD'] ?? null,
    'post' => $_POST,
    'raw' => file_get_contents('php://input')
]));

Do not display sensitive data such as passwords, tokens, or card numbers on the screen or in production logs. Logs should help identify issues without creating new risks.

If the request uses JSON, PHP does not automatically place its contents in $_POST. Data needs to be read from the request body and converted into an array:

$payload = json_decode(file_get_contents('php://input'), true);

After that, validate the type and existence of fields before executing the query. Error messages like "the email field is required" are much more useful than database errors that appear several steps later.

Use exceptions to prevent silent failures

PDO provides several error handling modes. The exception mode throws database failures as PDOException, making error tracking easier. PHP documentation notes that PDO::ERRMODE_EXCEPTION has been the default mode since PHP 8.0, but explicitly setting it still makes application configuration clearer.

$pdo = new PDO($dsn, $username, $password, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

In a development environment, display error details appropriately. In production, save technical details to logs and send general messages to users. Do not display table names, connection credentials, or server paths on public pages.

Test queries with the same data

A query that fails in the application may not necessarily fail when run in the database application. The cause is often parameter values, connections to different databases, or overly strict WHERE conditions.

Use prepared statements and bound parameters, rather than concatenating user input into SQL strings:

$stmt = $pdo->prepare(
    'SELECT id, name FROM users WHERE email = :email LIMIT 1'
);
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();

Prepared statements help separate the SQL template from input values. However, they are not a substitute for validation. Email still needs to be checked for format, IDs must be ensured to be reasonable numbers, and access rights must still be verified on the server side.

During debugging, log operation names and parameters that are safe to log. Avoid logging the entire query after sensitive parameters have been appended to it. The goal is to gain context, not to make the logs a copy of user data.

Use EXPLAIN for slow-feeling issues

If a query returns the correct data but the page is slow, do not immediately add servers or change many indexes. Run EXPLAIN to see the MySQL execution plan. This command shows how the optimizer estimates how to read tables, use indexes, and join data.

EXPLAIN
SELECT orders.id, orders.created_at
FROM orders
WHERE orders.user_id = 42
ORDER BY orders.created_at DESC
LIMIT 20;

Check whether columns frequently used in WHERE, JOIN, or ORDER BY have relevant indexes. Do not assume that every index always speeds up the application. Too many indexes also add overhead when writing data and can increase space usage.

For testing that requires comparing estimates with actual conditions, MySQL provides EXPLAIN ANALYZE for certain command types. Use it carefully and perform it in a safe environment, especially if the query does not only read data.

Differentiate API errors from display errors

An API that returns an HTTP 200 response does not necessarily send the correct data. The server might send JSON containing {...

Sources & further reading

– Rio Yotto @rioyotto