Skip to main content

Command Palette

Search for a command to run...

10 Common PHP Bugs in Real-Time Development (With Fixes)

These bugs don't appear in textbooks, but they will show up in production.

Updated
8 min read
10 Common PHP Bugs in Real-Time Development (With Fixes)
B
Founder of CodePractice | Full Stack Developer & Coding Trainer | Helping developers learn PHP, MySQL, JavaScript, Laravel, Python, C programming, C++ programming, Java, and modern web development.

PHP is a forgiving language — and that forgiveness is exactly what makes it dangerous in production.

Everything works perfectly on your local machine. Code review passes. Then a silent bug surfaces on a live server, in front of real users, on a real deadline.

These aren't theoretical mistakes from documentation. These are bugs that actually appear in login systems, e-commerce platforms, school management portals, and client projects.

Here are 10 of the most common PHP bugs found in real-time development — with the broken code, the correct fix, and a clear explanation of why it fails.


https://youtube.com/shorts/l6sDANRcWYs


Bug #1 — Variable Not Accessible Inside a Function

The Mistake

$username = "Rahul";

function greetUser() {
    echo "Hello, " . $username; // Undefined variable!
}

greetUser();

The Fix

// Clean approach — pass as parameter
function greetUser($username) {
    echo "Hello, " . $username;
}

greetUser("Rahul");

Why It Fails

PHP functions have completely isolated scope. Variables defined outside are invisible inside — unlike JavaScript. This silently breaks session handling and login logic where developers assume a variable is already available.


Bug #2 — Assignment Instead of Comparison

The Mistake

if($isLoggedIn = true) {
    echo "Welcome!"; // Always executes — even if user is not logged in
}

The Fix

if($isLoggedIn === true) {
    echo "Welcome!";
}

Why It Fails

= assigns a value. === compares both value AND type. When you write $isLoggedIn = true inside a condition, PHP assigns true first, then evaluates it — which is obviously always true. In an authentication context, this is a direct bypass vulnerability.


Bug #3 — strlen() on UTF-8 / Multibyte Text

The Mistake

$text = "नमस्ते";
echo strlen($text); // Returns 18 — WRONG

The Fix

echo mb_strlen($text, 'UTF-8'); // Returns 6 — CORRECT

Why It Fails

strlen() counts bytes, not characters. Each UTF-8 character can occupy 2–4 bytes. This silently breaks form validation, character limits, and SMS length calculations for any non-ASCII content.

Note: The same issue exists with substr(), strpos(), and strtolower() — always use their mb_ equivalents when working with multilingual text.


Bug #4 — Missing isset() Before Accessing POST/GET Data

The Mistake

echo $_POST['username']; // Undefined index on first load

The Fix

// Traditional approach
if(isset($_POST['username'])) {
    echo $_POST['username'];
}

// PHP 8+ shorthand
\(name = \)_POST['username'] ?? '';

Why It Fails

$_POST is only populated when a form is actually submitted. On first page load, after a refresh, or on direct URL access — the key simply doesn't exist. Every contact form, login form, and registration page needs this check without exception.


Bug #5 — Storing Plain Text Passwords

The Mistake

\(password = \)_POST['password'];
\(sql = "INSERT INTO users (password) VALUES ('\)password')";
// Plain text stored — NEVER do this

The Fix

// Store
\(hashed = password_hash(\)_POST['password'], PASSWORD_BCRYPT);
\(stmt = \)pdo->prepare("INSERT INTO users (password) VALUES (?)");
\(stmt->execute([\)hashed]);

// Verify
if(password_verify(\(inputPassword, \)hashedFromDB)) {
    echo "Login successful";
}

Why It Fails

If your database is ever compromised — and it happens — plain text passwords give attackers instant access to every user account. password_hash() generates a strong salted hash. password_verify() handles comparison safely. This is OWASP Top 10. It is not optional.


Bug #6 — SQL Injection via Raw User Input

The Mistake

\(id = \)_GET['id'];
\(result = mysqli_query(\)conn, "SELECT * FROM users WHERE id = $id");
// ?id=1 OR 1=1 — dumps your entire users table

The Fix

\(stmt = \)pdo->prepare("SELECT * FROM users WHERE id = ?");
\(stmt->execute([\)_GET['id']]);
\(user = \)stmt->fetch();

Why It Fails

User input goes directly into the query string. A crafted input can read, modify, or delete your entire database. PDO prepared statements separate input from query structure completely — input is always treated as data, never as executable SQL.


Bug #7 — Header Redirect Without exit()

The Mistake

if(!$isAdmin) {
    header("Location: login.php");
    deleteAllRecords(); // This WILL still execute
}

The Fix

if(!$isAdmin) {
    header("Location: login.php");
    exit();
}

Why It Fails

header() sets the redirect but does NOT stop PHP execution. The browser leaves the page, but the server keeps running the rest of the script. Any sensitive operations after the redirect — deletions, admin actions, file modifications — will still execute. Always add exit() immediately after every redirect.


Bug #8 — file_get_contents() on Large Files

The Mistake

$data = file_get_contents("students_data.csv"); // 50MB into RAM
\(lines = explode("\n", \)data);

foreach(\(lines as \)line) {
    // process
}

The Fix

$handle = fopen("students_data.csv", "r");

if($handle !== false) {
    while((\(line = fgets(\)handle)) !== false) {
        \(fields = str_getcsv(\)line);
        // Process one row at a time
    }
    fclose($handle);
}

Why It Fails

file_get_contents() loads the entire file into memory at once. A 50MB file means 50MB+ RAM — PHP hits its memory limit and crashes. fgets() reads one line at a time, keeping memory usage minimal regardless of file size. Essential for bulk imports, product uploads, and data processing in production.


Bug #9 — session_start() After Output

The Mistake

echo "Welcome to our site!";
session_start(); // Error: headers already sent

The Fix

<?php
session_start(); // Must be FIRST — before any output
?>
<!DOCTYPE html>
<html>
<body>
    Welcome to our site!
</body>
</html>

Why It Fails

PHP sends HTTP headers before any output reaches the browser. Once even a single character of output is sent — including whitespace before <?php — headers are locked. session_start() sets a cookie header, so it must come before any output. Hidden causes include BOM characters in file encoding and whitespace in included files.


Bug #10 — PDO Without Error Mode Set

The Mistake

\(pdo = new PDO("mysql:host=localhost;dbname=mydb", \)user, $pass);
\(stmt = \)pdo->query("SELECT * FROM non_existing_table");
// Returns false silently — no error, no clue

The Fix

try {
    $pdo = new PDO(
        "mysql:host=localhost;dbname=mydb",
        $user,
        $pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );
} catch(PDOException $e) {
    error_log("DB Error: " . $e->getMessage()); // Log server-side
    die("Something went wrong. Please try again."); // Generic for users
}

Why It Fails

PDO's default error mode swallows failures silently. Queries return false with no exception and no message — leaving you completely in the dark. Setting ERRMODE_EXCEPTION turns every database failure into a catchable exception. Always log the real error server-side and show users a generic message — never expose raw exception output in production.


Quick Reference Table

# Bug Category Risk Level
1 Variable scope in function Logic Medium
2 = instead of === Type Critical
3 strlen() on UTF-8 text Type Medium
4 Missing isset() on POST/GET Logic Medium
5 Plain text password storage Security Critical
6 SQL Injection via raw input Security Critical
7 Redirect without exit() Security High
8 file_get_contents() on large files Performance High
9 session_start() after output Logic Medium
10 PDO without error mode Database High

The Common Thread

Most of these bugs share three root causes: unvalidated input, unhandled errors, and wrong assumptions about state.

Build these three habits and you eliminate the majority of production PHP bugs:

  • Always validate input

  • Always escape output

  • Always handle errors explicitly


Conclusion

The bugs on this list are dangerous precisely because they're invisible locally. They surface only when real users, real data, and real server environments come together.

Bookmark this article. Share it with your team. And next time you're reviewing PHP code — run through this list before pushing to production.

Found a bug we missed? Drop it in the comments — this list gets updated from real projects.