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

Iniciado por joomlamz, Hoje at 06:25

Respostas: 0   |   Visualizações: 1

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

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



Tópico: PHP Bugs #21 to #30 — Common Mistakes Every PHP Developer Must Know published
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------
If you've been following this series — Part 1 (Bugs #1–10) covered type juggling and variable scoping, Part 2 (Bugs #11–20) tackled form handling and session quirks — now Part 3 is where things get genuinely dangerous.

Bugs #21 to #30 aren't just annoying syntax mistakes. Some are active security vulnerabilities. A few will crash your server under load. Others silently corrupt data without throwing a single error.

Let's go through each one.



Bug #21 — strlen() Giving Wrong Count for Multibyte Text


// ❌ Wrong
echo strlen("नमस्ते"); // Output: 18 — but it has only 6 characters!

strlen() counts bytes, not characters. In UTF-8, each Hindi character takes 3 bytes. Six characters × 3 bytes = 18.

// ✅ Correct
echo mb_strlen("नमस्ते", 'UTF-8'); // Output: 6

Once you're working with multibyte text, switch the entire function family:


substr() → mb_substr()


strtolower() → mb_strtolower()


strpos() → mb_strpos()

Any form that accepts multilingual input needs mb_ functions.



Bug #22 — API Response Returning Null


// ❌ Wrong — no error checking at all
$response = file_get_contents("https://api.example.com/data");
$data = json_decode($response);
echo $data->name; // Null!

Three things can fail here and this code catches none of them:


file_get_contents() might have failed silently

• The response might not be valid JSON

• The structure might be different from what you expected

// ✅ Correct
$response = file_get_contents("https://api.example.com/data");

if ($response === false) {
die("API request failed");
}

$data = json_decode($response, true); // true = associative array

if (json_last_error() !== JSON_ERROR_NONE) {
die("JSON decode error: " . json_last_error_msg());
}

echo $data['name'];

In production, replace die() with proper logging. Never expose raw API errors to users.



Bug #23 — Cookie Disappears After Browser Close


// ❌ Wrong — creates a session cookie (lives in memory only)
setcookie("user", "John");

No expiry = browser memory only. Tab closes → cookie gone.

// ✅ Correct — with security flags (PHP 7.3+)
setcookie("user", "John", [
'expires'  => time() + 86400 * 30, // 30 days
'secure'   => true,                // HTTPS only
'httponly' => true,                // No JS access (XSS protection)
'samesite' => 'Strict'
]);

The httponly flag is crucial — it prevents JavaScript from reading the cookie and protects against XSS attacks stealing session data.



Bug #24 — File Upload Accepting PHP Files 🚨


This isn't just a bug. It's a critical security vulnerability.

// ❌ Dangerously wrong
if (pathinfo($file, PATHINFO_EXTENSION) == 'jpg') {
move_uploaded_file(...); // shell.php renamed to shell.jpg bypasses this
}

Renaming shell.php to shell.jpg takes two seconds. Extension checks are useless for security.

// ✅ Correct — check actual file content
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime  = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);

if (in_array($mime, $allowed_types)) {
$safe_name = uniqid() . '_' . time() . '.jpg'; // Never keep original filename
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $safe_name);
} else {
die("Only image files are allowed.");
}

Extra layer: Disable PHP execution in your uploads folder via .htaccess:

<Directory /var/www/html/uploads>
php_flag engine off
</Directory>

Even if a PHP file sneaks in — it won't execute.



Bug #25 — number_format() Giving Wrong Calculation Results


// ❌ Wrong
$price = "1,299.00";
echo number_format($price * 1.18); // Calculates on 1, not 1299!

PHP stops reading the string at the comma and converts "1,299.00" to the float 1. Your 18% tax runs on ₹1.00 instead of ₹1299.00.

// ✅ Correct — strip comma first, then calculate
$price = floatval(str_replace(',', '', "1,299.00")); // 1299.00
echo number_format($price * 1.18, 2); // 1532.82

Rule: Never store prices as formatted strings in your database. Store raw numbers, apply number_format() only at the display layer.



Bug #26 — Class Variable Shared Across All Objects


// ❌ Wrong
class Cart {
static $items = []; // Shared by ALL Cart objects
}

$cart1 = new Cart();
$cart1::$items[] = "Laptop";

$cart2 = new Cart();
print_r($cart2::$items); // ["Laptop"] — What?!

A static property belongs to the class, not to individual instances. Every Cart object shares the same $items.

// ✅ Correct
class Cart {
public $items = []; // Each object gets its own copy
}

$cart1 = new Cart();
$cart1->items[] = "Laptop";

$cart2 = new Cart();
print_r($cart2->items); // [] — Correct

Use static only for values that genuinely belong to the class as a whole — like a shared counter or config, not per-user data.



Bug #27 — Code Still Runs After a Redirect


// ❌ Wrong — and a serious security hole
if (!$isAdmin) {
header("Location: login.php");
deleteAllUsers(); // This STILL runs on the server!
}

header() sends an HTTP instruction to the browser. It does not stop the PHP script. The browser redirects — but every line after keeps executing on the server.

// ✅ Correct
if (!$isAdmin) {
header("Location: login.php");
exit(); // Script stops here. Done.
}

An attacker who understands HTTP can send a raw request and receive the full server response before the redirect happens. Always pair header("Location: ...") with exit(). No exceptions.



Bug #28 — Search Query Vulnerable to SQL Injection


// ❌ Wrong — never do this
$search = $_GET['q'];
$sql = "SELECT * FROM products WHERE name LIKE '%$search%'";

User types % → gets every product. Types ' → SQL error. Types ' OR '1'='1 → manipulates your entire query.

// ✅ Correct — prepared statements
$search = $_GET['q'];
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ?");
$stmt->execute(['%' . $search . '%']);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

The ? placeholder keeps user input completely separate from query logic. It can never modify the query structure — no matter what characters the user types.

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



Bug #29 — Large Files Crashing PHP With Memory Errors


// ❌ Wrong — loads entire file into RAM
$data = file_get_contents("large_export.csv"); // 500MB file = 500MB RAM. Fatal.

file_get_contents() loads the entire file into a PHP string. Most PHP configs cap memory at 128–256MB. Your script crashes before processing a single row.

// ✅ Correct — stream line by line
$handle = fopen("large_export.csv", "r");

if ($handle !== false) {
while (($line = fgets($handle)) !== false) {
$row = str_getcsv($line);
processRow($row); // One row at a time, flat memory usage
}
fclose($handle);
}

fgets() reads one line at a time — memory stays nearly flat regardless of file size. For huge database imports, look into MySQL's LOAD DATA INFILE — it's significantly faster than PHP for bulk inserts.



Bug #30 — PDO Not Reporting Database Errors


// ❌ Wrong — silent failure by default
$pdo = new PDO("mysql:host=localhost;dbname=mydb", $user, $pass);
$stmt = $pdo->query("SELECT * FROM nonexistent_table");
// Returns false. No exception. No warning. Nothing.

PDO's default mode is silent. Errors return false and execution continues. If you're not manually checking every return value, failures disappear without a trace.

// ✅ Correct — set error mode at connection time
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,
PDO::ATTR_EMULATE_PREPARES   => false,
]
);
} catch (PDOException $e) {
error_log($e->getMessage()); // Log the real error
die("A database error occurred. Please try again.");
}

Production rule: Never echo $e->getMessage() to users. It may expose table names, column names, or connection details. Log it privately, show a generic message publicly.



Quick Reference


Bug
The Mistake
The Fix

#21

strlen() on multibyte text

mb_strlen() with encoding

#22
API response null, no validation
Check false, use json_last_error()

#23
Cookie deleted on browser close
Pass expiry time to setcookie()

#24
File upload accepting PHP files
Check MIME type with finfo_file()

#25
Comma-formatted price wrong calc
Strip comma, then floatval()

#26
All objects share same static items
Remove static, use instance property

#27
Code runs after redirect
Add exit() after every header()

#28
SQL injection via search input
Prepared statements with PDO

#29
Memory crash on large CSV
Stream with fgets() line by line

#30
PDO errors invisible
Set ERRMODE_EXCEPTION at connection



The Common Thread


Every bug here follows the same pattern: assuming something worked when it didn't. PHP quietly lets you be wrong — no warning, no crash, just subtly broken behaviour in production.

The fix is the same for all of them: be explicit. Check return values. Set error modes. Validate inputs. Treat anything from outside your code as untrusted until proven otherwise.

Full article with deeper explanations: codepractice.in

Next in the series: PHP session security, password hashing, and CSRF protection.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: