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
86: Handling Transactions with PDO
I want to show you a snippet of code that looks perfectly reasonable at first glance. Imagine you're building a simple internal wallet system for a site where users can send credits to each other. You've got your PDO connection ready, and you write this logic to handle a transfer:
$amount = 50.00;
$fromUser = 12;
$toUser = 45;
// Subtract from sender
$pdo->exec("UPDATE users SET balance = balance - $amount WHERE id = $fromUser");
// Add to receiver
$pdo->exec("UPDATE users SET balance = balance + $amount WHERE id = $toUser");
echo "Transfer successful!";
If you run this during a happy-path test, it works. But here is the nightmare scenario: what happens if the server loses power, the database connection drops, or a constraint is triggered exactly between those two lines of code? The first user loses 50 credits, but the second user never receives them. The money effectively vanishes into the void. In the industry, we call this a lack of atomicity.
The Danger of Partial Execution
The problem here is that by default, PDO (and most databases) operates in "autocommit" mode. Every single exec() or execute() call is treated as a complete, permanent transaction. In a financial operation—or any operation where two or more tables must stay in sync—this is a recipe for corrupted data.
You can't just "hope" the second query works. You need a way to tell the database: "Hold onto these changes in a temporary state. If everything goes perfectly, make them permanent. If even one thing trips up, pretend none of this ever happened."
Guaranteeing Consistency with beginTransaction
To fix this, we wrap the operations in a transaction. I always recommend using a try-catch block here because transactions are useless if you don't have a plan for when things go wrong.
try {
// 1. Turn off autocommit
$pdo->beginTransaction();
$pdo->exec("UPDATE users SET balance = balance - $amount WHERE id = $fromUser");
$pdo->exec("UPDATE users SET balance = balance + $amount WHERE id = $toUser");
// 2. If we reached here, no exceptions were thrown. Make it permanent.
$pdo->commit();
echo "Transfer successful!";
} catch (Exception $e) {
// 3. Something went wrong. Undo everything since beginTransaction().
$pdo->rollBack();
echo "Transfer failed: " . $e->getMessage();
}
Now, if that second update fails, the rollBack() method tells the database to discard the first update. The sender gets their money back instantly. It's all or nothing.
A Quick Note on Storage Engines
I've seen developers pull their hair out because beginTransaction() didn't seem to be doing anything. If you're using MySQL, check your table engine. Transactions only work with InnoDB. If your tables are using the old MyISAM engine, MySQL will silently ignore your transaction commands and keep autocommitting. It's a legacy quirk, but it's a common trap.
When to Use This Pattern
You don't need transactions for every single query—that would add unnecessary overhead. Use them when you have "dependent writes." For example:
- Creating an
orderrecord and simultaneously decrementingproduct_stock. - Creating a
useraccount and immediately inserting a defaultuser_profilerecord. - Moving a file record from a
pendingtable to anarchivedtable.
📋 Practical Task
Exercise: Building a Fail-Safe Inventory Deduction System
You are tasked with creating a checkout script for an e-commerce store. You have two tables: orders and products.
Write a PHP script using PDO that does the following inside a single transaction:
- Inserts a new row into the
orderstable (columns:user_id,total_price). - Updates the
productstable to subtract the quantity purchased from thestock_countfor a specific product. - The Twist: Before committing, check if the
stock_counthas dropped below zero. If it has, throw a customExceptionto trigger the rollback, ensuring that an order is never created for an item that is out of stock.
Your code must include the beginTransaction(), commit(), and rollBack() methods within a try-catch block.
There are no comments for now.