DEV Community

Cover image for 10 Common PHP Bugs in Real-Time Development (With Fixes)
Bikki Singh
Bikki Singh

Posted on • Originally published at codepractice.in

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

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

Everything works fine locally. Code review passes. Then a silent bug surfaces on the live server, in front of real users, on a deadline.

These aren't bugs from documentation or textbooks. These are bugs that appear in login systems, e-commerce platforms, school portals, and client projects — bugs I've documented from real-time development experience.

Here are 10 of the most common ones, with the broken code, the fix, and a clear explanation of why it fails.


Bug #1 — Variable Not Accessible Inside a Function

// ❌ Broken
$username = "Rahul";

function greetUser() {
    echo "Hello, " . $username; // Undefined variable
}
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed — pass as parameter (cleaner approach)
function greetUser($username) {
    echo "Hello, " . $username;
}
greetUser("Rahul");
Enter fullscreen mode Exit fullscreen mode

Why it fails: PHP functions have isolated scope. Variables defined outside are not automatically available inside — unlike JavaScript. This silently breaks session handling and login systems.


Bug #2 — Assignment Instead of Comparison

// ❌ Broken — always true
if($isLoggedIn = true) {
    echo "Welcome!";
}
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
if($isLoggedIn === true) {
    echo "Welcome!";
}
Enter fullscreen mode Exit fullscreen mode

Why it fails: = assigns a value. === compares value AND type. The condition evaluates the assigned value (true), so it's always true — regardless of actual login state. In production, this is an authentication bypass.


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

// ❌ Broken
$text = "नमस्ते";
echo strlen($text); // Returns 18, not 6
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
echo mb_strlen($text, 'UTF-8'); // Returns 6
Enter fullscreen mode Exit fullscreen mode

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

Same issue exists with substr(), strpos(), strtolower() — always use their mb_ equivalents.


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

// ❌ Broken
echo $_POST['username']; // Undefined index on first load
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
if(isset($_POST['username'])) {
    echo $_POST['username'];
}

// PHP 8+ shorthand
$name = $_POST['username'] ?? '';
Enter fullscreen mode Exit fullscreen mode

Why it fails: $_POST is only populated on form submission. On first load, refresh, or direct URL access — the key doesn't exist. Every form handler needs this check.


Bug #5 — Storing Plain Text Passwords

// ❌ Never do this
$sql = "INSERT INTO users (password) VALUES ('$password')";
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
$hashed = password_hash($_POST['password'], PASSWORD_BCRYPT);
$stmt = $pdo->prepare("INSERT INTO users (password) VALUES (?)");
$stmt->execute([$hashed]);

// Verification
if(password_verify($inputPassword, $hashedFromDB)) {
    echo "Login successful";
}
Enter fullscreen mode Exit fullscreen mode

Why it fails: If your database is ever compromised, plain text passwords give attackers instant access to every account. password_hash() generates a strong salted hash. This is OWASP Top 10 — not optional.


Bug #6 — SQL Injection via Raw User Input

// ❌ Broken — wide open to injection
$id = $_GET['id'];
$result = mysqli_query($conn, "SELECT * FROM users WHERE id = $id");
// ?id=1 OR 1=1 dumps your entire table
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed — PDO prepared statements
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
$user = $stmt->fetch();
Enter fullscreen mode Exit fullscreen mode

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


Bug #7 — Header Redirect Without exit()

// ❌ Broken — script keeps running after redirect
if(!$isAdmin) {
    header("Location: login.php");
    deleteAllRecords(); // This WILL execute
}
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
if(!$isAdmin) {
    header("Location: login.php");
    exit();
}
Enter fullscreen mode Exit fullscreen mode

Why it fails: header() sets the redirect but does not stop PHP execution. The browser leaves, but the server keeps running the rest of the script. Always add exit() after every redirect — especially in admin panels and access control logic.


Bug #8 — file_get_contents() on Large Files

// ❌ Broken — crashes on large files
$data = file_get_contents("students_data.csv"); // 50MB into RAM
$lines = explode("\n", $data);
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed — stream line by line
$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);
}
Enter fullscreen mode Exit fullscreen mode

Why it fails: file_get_contents() loads the entire file into memory. For large CSVs or data exports, PHP hits its memory limit and crashes. fgets() reads one line at a time — memory stays flat regardless of file size.


Bug #9 — session_start() After Output

// ❌ Broken
echo "Welcome!";
session_start(); // Error: headers already sent
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed — session_start() must be first
<?php
session_start();
?>
<!DOCTYPE html>
<html>
<body>Welcome!</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Why it fails: PHP sends HTTP headers before any output. Once a single character is output — even whitespace before <?php — headers are locked. session_start() sets a cookie header, so it must come before any output. Hidden causes: BOM in file encoding, whitespace in included files.


Bug #10 — PDO Without Error Mode Set

// ❌ Broken — fails silently
$pdo = new PDO("mysql:host=localhost;dbname=mydb", $user, $pass);
$stmt = $pdo->query("SELECT * FROM non_existing_table");
// Returns false with no error — confusion guaranteed
Enter fullscreen mode Exit fullscreen mode
// ✅ Fixed
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 it
    die("Something went wrong.");               // Generic message for user
}
Enter fullscreen mode Exit fullscreen mode

Why it fails: PDO's default mode swallows errors silently. Setting ERRMODE_EXCEPTION turns every database failure into a catchable exception. Always log the real error, show users a generic message — never echo raw exception output in production.


Quick Reference

# Bug Category Risk
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 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

Full article with deeper explanations: 10 Common PHP Bugs in Real-Time Development (With Fixes)

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

Top comments (0)