PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
85: Writing Prepared Statements Safely
I've sat through countless code reviews where a developer points to a block of code and says, "I'm using prepared statements, so this is 100% injection-proof." They feel a sense of security because they've swapped $stmt->query() for $stmt->prepare(). But here is the cold, hard truth: prepared statements are not a magic shield that makes any variable safe to drop into a query. They only protect you when you are binding values.
The "Prepared Statements Stop All Injection" Myth
The most dangerous misconception I see is the belief that you can use placeholders (? or :name) for anything in your SQL string. Let's look at a common scenario: you want to let a user choose which column to sort their search results by.
// WARNING: This will NOT work and is a common mistake
$sortColumn = $_GET['sort']; // User sends 'price' or 'created_at'
$sql = "SELECT * FROM products ORDER BY :column DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute(['column' => $sortColumn]);
If you run this, you'll likely get a result, but it won't be sorted. Why? Because the database driver treats the bound parameter as a string literal. The database sees ORDER BY 'price' DESC. Sorting by a static string constant does nothing. Now, when the developer realizes the placeholder doesn't work, they often "fix" it by doing this:
// DANGER: This is a textbook SQL injection vulnerability
$sortColumn = $_GET['sort'];
$sql = "SELECT * FROM products ORDER BY $sortColumn DESC";
$stmt = $pdo->prepare($sql); // Preparing a string that is already tainted!
$stmt->execute();
By concatenating the variable directly into the query string, you've just handed the keys to your database to anyone with a browser. The fact that you called prepare() afterwards is irrelevant because the malicious payload is already baked into the SQL command before it ever hits the prepare method.
Separating Data from Structure
To write these safely, you have to understand the boundary between data and structure. Prepared statements are designed to protect data (the values in a WHERE clause, the values being INSERTED). They cannot be used for structure—things like table names, column names, or SQL keywords (ASC/DESC).
If you need a dynamic column name, you cannot bind it. You have to handle it in your PHP logic before the query is built. I always tell my juniors: if it's not a value, it doesn't belong in a placeholder.
Hardening Dynamic Identifiers with Allow-lists
So, how do you actually solve the sorting problem without opening a security hole? You use an allow-list. You define exactly which inputs are acceptable, and if the user provides anything else, you either throw an error or fall back to a safe default. I prefer the default approach because it prevents the app from crashing just because a user messed up a URL parameter.
$allowedSorts = ['price', 'created_at', 'rating'];
$userInput = $_GET['sort'] ?? 'created_at';
// If the input isn't in our list, force it to the default
$sortColumn = in_array($userInput, $allowedSorts) ? $userInput : 'created_at';
// Now it is safe to concatenate because $sortColumn is guaranteed
// to be one of our three hard-coded strings.
$sql = "SELECT * FROM products ORDER BY $sortColumn DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
By using in_array(), you've created a strict gate. The user can send 'price; DROP TABLE users;--' all they want, but your code will simply see that it's not in the allow-list and reset it to 'created_at'. This is the only way to handle dynamic identifiers safely.
📋 Practical Task
Build a Secure Dynamic Product Filter
You are building a product gallery where users can filter items by a specific category and sort them by different criteria. You need to implement this using a mix of prepared statement placeholders for the data and an allow-list for the structure.
Requirements:
- Create a PHP script that accepts two
$_GETparameters:categoryandsort_by. - The
categoryshould be handled via a prepared statement placeholder (?or:category) since it is a value. - The
sort_byparameter must be validated against an allow-list containing'price','name', and'stock_level'. If the input is invalid or missing, default it to'name'. - Write the final PDO code to execute the query:
SELECT * FROM products WHERE category = [category] ORDER BY [sort_by] ASC.
Goal: Ensure that no matter what is passed into the URL, the query cannot be manipulated to access other tables or bypass the category filter.
There are no comments for now.