Skip to main content

Command Palette

Search for a command to run...

PHP Bugs 21 to 30 — Common Mistakes Every PHP Developer Must Know

Updated
4 min read
PHP Bugs 21 to 30 — Common Mistakes Every PHP Developer Must Know
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.

If you've been writing PHP for a while, you've probably hit a few of these without realizing what was actually happening under the hood. This is Part 3 of a series I've been running on CodePractice — Part 1 covered type juggling and variable scoping, Part 2 covered form handling and session quirks. This batch is heavier. A few of these are genuine security vulnerabilities. One can take your server down under load. A couple corrupt data without throwing a single error.

Original post with the full quick-reference table and FAQ: PHP Bugs 21 to 30 — Common Mistakes Every PHP Developer Must Know

Let's go through them.

#21 — strlen() on multibyte text

echo strlen("नमस्ते"); // 18, not 6

strlen() counts bytes, not characters. UTF-8 encodes non-Latin characters as 2–3 bytes each. Swap in mb_strlen($string, 'UTF-8') — and once you're handling multibyte input, switch the whole family: mb_substr, mb_strtolower, mb_strpos.

#22 — Trusting an API response blindly

$response = file_get_contents("https://api.example.com/data");
\(data = json_decode(\)response);
echo $data->name; // null, and you have no idea why

Three things can fail here and none are checked: the request itself, JSON validity, and response structure. Check $response === false, decode with true for an associative array, and check json_last_error().

#23 — Cookies dying on browser close

No expiry argument means a session cookie. Pass time() + 86400 * 30 for persistence, or use the options array on PHP 7.3+ to set secure, httponly, and samesite at the same time.

#24 — File upload validation via extension

if (pathinfo($file, PATHINFO_EXTENSION) == 'jpg') {
    move_uploaded_file(...);
}

Renaming shell.php to shell.jpg defeats this instantly. Use finfo_file() to check actual file bytes, randomize the stored filename, and disable PHP execution in the uploads directory via .htaccess as a second layer.

#25 — number_format() on comma-formatted strings

$price = "1,299.00";
echo number_format($price * 1.18); // wrong — PHP reads "1" up to the comma

Strip the comma, floatval() it, then multiply. Don't store prices as formatted strings — formatting belongs at the display layer only.

#26 — static properties shared across instances

class Cart {
    static $items = []; // shared by every Cart object
}

This is the classic shopping-cart bug — add an item to one cart, it shows up in another. Use a regular instance property unless the value genuinely belongs to the class itself (an object counter, shared config).

#27 — Execution continuing after a redirect header

if (!$isAdmin) {
    header("Location: login.php");
    deleteAllUsers(); // still executes!
}

header() sends a redirect instruction to the browser — it does not stop the script. Every line after it still runs server-side. Always pair header("Location: ...") with exit(). This one has real security implications: a raw HTTP request (not a browser following the redirect) can trigger the code that should've been blocked.

#28 — SQL injection via unsanitized search input

\(sql = "SELECT * FROM products WHERE name LIKE '%\)search%'";

Prepared statements fix this completely:

\(stmt = \)pdo->prepare("SELECT * FROM products WHERE name LIKE ?");
\(stmt->execute(['%' . \)search . '%']);

Note the % wildcards go outside the placeholder in PHP, not inside the query string.

#29 — Loading large files entirely into memory

$data = file_get_contents("large_export.csv"); // 500MB file = 500MB RAM

Stream instead:

$handle = fopen("large_export.csv", "r");
while ((\(line = fgets(\)handle)) !== false) {
    processRow(str_getcsv($line));
}
fclose($handle);

Memory stays flat regardless of file size. For very large imports, LOAD DATA INFILE in MySQL is faster than PHP either way.

#30 — PDO failing silently

PDO defaults to silent mode — failed queries return false, no exception. Fix it at connection time:

\(pdo = new PDO(\)dsn, \(user, \)pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

Catch PDOException, log the real message, and never echo it directly to users.

The pattern

Nearly every bug here comes from PHP assuming something worked when it didn't — and quietly letting you be wrong. The fix is consistent: validate assumptions, check return values, set explicit error modes, and treat all external input as untrusted.

Full breakdown with the wrong/correct code for each bug plus a quick-reference table is on the original post: PHP Bugs 21 to 30 — Common Mistakes Every PHP Developer Must Know


Originally published on CodePractice.

#php #webdev #backend #security #programming

More from this blog