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
44: Static Analysis with PHPStan
A few years ago, I was reviewing a pull request for a payment processing module. A junior dev had changed a method in our AccountRepository to return ?User instead of User because, logically, a user might not actually exist in the database. It made perfect sense. However, they missed one tiny call to that method in a legacy reporting script. We merged it, and an hour later, the logs lit up like a Christmas tree with Fatal error: Uncaught Error: Call to a member function getEmail() on null. The script had crashed in production because we relied on a human to remember every single place that method was called.
That's the gap that static analysis fills. In a language as flexible as PHP, it's incredibly easy to assume a variable is a string when it's actually null or false. PHPStan doesn't run your code; it reads it. It builds a map of your types and logic, then screams at you before you ever hit "git push" that you're trying to call a method on something that might not exist.
Closing the Gap Between Intent and Reality
When you first install PHPStan via Composer, you'll notice it doesn't just give you a "pass/fail." It operates on levels from 0 to 9. Level 0 is basic—it catches obvious mistakes like calling a function that doesn't exist. Level 9 is "pedantic mode," where it demands absolute certainty about every single type, including mixed arrays.
I usually recommend starting at Level 5. It's the sweet spot where you catch the most dangerous bugs—like the nullable return value I mentioned—without getting bogged down in the minutiae of every single docblock. Here is a typical scenario PHPStan would catch that your IDE might miss if the types are complex:
public function getDiscountedPrice(Product $product, ?User $user): float
{
// If $user is null, this will crash at runtime.
// PHPStan Level 5 will flag this immediately.
if ($user->isPremium()) {
return $product->getPrice() * 0.8;
}
return $product->getPrice();
}
To fix this, you'd have to wrap that call in a null check. It feels like extra work in the moment, but it's the difference between a stable app and a 3:00 AM wake-up call.
Taming "Mixed" and Magic with PHPDocs
PHP's type system has come a long way, but we still deal with arrays. The biggest headache in PHP is the array type—it tells you nothing about what's inside the array. Is it a list of integers? A map of strings to objects? PHPStan allows you to use "generics-style" PHPDoc annotations to bring type safety to your collections.
I've spent too many hours debugging an array of IDs that accidentally contained a few null values. By using a specific annotation, you can force PHPStan to verify the contents of your loops:
/**
* @param array<int, User> $users
* @return array<int, string>
*/
public function getUserEmails(array $users): array
{
$emails = [];
foreach ($users as $user) {
// PHPStan now knows $user is an instance of User
$emails[] = $user->getEmail();
}
return $emails;
}
If you accidentally pass an array of strings to this method, PHPStan will flag it. You're essentially creating a contract that is enforced by the tool, even if the PHP engine itself is too permissive to care.
Integrating Analysis into Your Workflow
Static analysis is useless if you only run it once a month. I treat PHPStan like a compiler. If the analysis fails, the code isn't finished. The most effective way to use it is to add it to your CI/CD pipeline or as a pre-commit hook.
One tip: don't try to jump to Level 9 on a legacy project. You will be greeted with 4,000 errors and you'll want to quit software engineering entirely. Instead, use the phpstan-baseline.neon feature. This "ignores" all existing errors in your current codebase but flags any new errors you introduce. It allows you to stop the bleeding immediately while cleaning up the old technical debt incrementally.
📋 Practical Task
Exercise: Squashing Type Errors in the OrderProcessor Service
You have been handed a OrderProcessor class that is causing intermittent crashes in production. The code is syntactically correct, so PHP doesn't complain until the code actually runs. Your goal is to use PHPStan's logic to find and fix the bugs.
The Scenario:
The processOrder method takes an array of items and an optional discount code. However, the code ignores the possibility that the discount code might be invalid or that the items array might be empty/malformed.
class OrderProcessor {
public function processOrder(array $items, ?string $discountCode): float {
$total = 0;
foreach ($items as $item) {
// BUG: $item is assumed to be an object with getPrice()
$total += $item->getPrice();
}
if ($discountCode) {
// BUG: getDiscountAmount could return null if code is expired
$total -= $this->getDiscountAmount($discountCode);
}
return $total;
}
private function getDiscountAmount(string $code): ?float {
$discounts = ['SAVE10' => 10.0, 'WELCOME' => 5.0];
return $discounts[$code] ?? null;
}
}
Your Task:
- Rewrite the
processOrdermethod so that it would pass PHPStan Level 6. - Add a PHPDoc annotation to the
$itemsparameter to specify that it must be an array ofOrderItemobjects. - Implement a null check for the result of
getDiscountAmountto ensure you aren't subtractingnullfrom afloat. - Ensure the method handles the case where
$itemsmight be empty without returning an unexpected type.
There are no comments for now.