10 Common PHP Bugs in Real-Time Development (With Fixes)
These bugs don't appear in textbooks, but they will show up in production.

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(), andstrtolower()— always use theirmb_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.





