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
182: Static Analysis with Psalm
Why do I need Psalm if I'm already using strict types in PHP 8?
It's a fair question. If you've got declare(strict_types=1); at the top of your files and you're using type hints for your arguments and return values, you might feel like you've got it covered. But here is the thing: PHP's type system is a runtime system. It tells you something is wrong only when the code actually executes and hits that line.
Psalm is a static analysis tool. It looks at your code without running it. It catches the "what if" scenarios that type hints miss. For example, look at this snippet:
public function getUserName(?User $user): string {
return $user->getName();
}
PHP won't complain about this until you actually pass a null value into the function, at which point your app crashes with a fatal error. Psalm, however, will flag this immediately. It'll tell you: "PossiblyNullReference: Cannot call method getName on possibly null value of type User|null." It forces you to handle the null case before you even commit the code.
How do I deal with a massive list of errors in an old project?
I've been there. You install Psalm on a legacy project, run it for the first time, and it returns 4,000 errors. Your first instinct is to uninstall it and pretend it never happened. Don't do that.
The secret weapon here is the Baseline. Instead of spending three weeks fixing every single type mismatch in a 5-year-old codebase, you can tell Psalm to "ignore everything that currently exists."
You run a command to generate a baseline file (usually psalm-baseline.xml). Psalm records every existing error and essentially says, "Okay, I know these 4,000 things are broken; I'll stop bothering you about them." From that moment on, Psalm only screams if you introduce new bugs or touch a line of code and make it worse. It allows you to implement strict standards for all new features without having to rewrite your entire history.
Can Psalm handle types that PHP doesn't actually support?
Yes, and this is where Psalm really starts to feel like a superpower. PHP's type system is relatively blunt. You can say something is an array, but you can't natively say "this is an array of User objects where the keys are strings."
Psalm uses docblock annotations to give you "generics-lite." If you're building a repository, you can be way more specific than PHP allows:
/**
* @return array<string, User>
*/
public function findAllUsers(): array {
// Psalm now knows that every value in this array is a User object
// and every key is a string.
return $this->db->fetchAllUsers();
}
If you try to treat a value in that array as an integer later in your code, Psalm will catch it. I personally find this indispensable for avoiding those annoying is_a() or instanceof checks that clutter up the business logic just to satisfy the IDE.
What should I do when Psalm is wrong (or just being too pedantic)?
Look, Psalm is smart, but it's not psychic. Occasionally, you'll write a piece of logic that you know is safe, but Psalm can't prove it. Maybe you're doing some complex array manipulation that you've tested thoroughly, but Psalm is still insisting a variable might be null.
When you've double-checked your logic and you're sure you're right, don't fight the tool by changing your architecture just to please it. Use a suppression annotation. You can tell Psalm to shut up for one specific line:
/** @psalm-suppress PossiblyNullReference */
return $user->getName();
I recommend using this sparingly. If you find yourself suppressing the same error ten times in one class, it's usually a sign that your types are lying to Psalm, and you should probably fix the type hints instead of silencing the alarm.
📋 Practical Task
Exercise: Silencing the Noise in the OrderRepository
You have been handed a legacy OrderRepository class. Psalm is currently flagging three major issues: a possible null reference, an imprecise array return type, and a variable that is being used before it is guaranteed to be initialized.
Your Task: Modify the code below to satisfy Psalm. You must:
- Add a Psalm-specific docblock to
findAllPaidOrdersso Psalm knows it returns a list ofOrderobjects. - Fix the
findOrderByIdmethod so it handles the potentialnullreturn from the database before callingcalculateTotal(). - Use a
@psalm-suppressannotation on the line where the developer used a "magic" property that Psalm can't track, but we know exists in the database layer.
class OrderRepository {
public function findAllPaidOrders(): array {
// TODO: Add Psalm annotation for array of Order objects
return $this->db->query("SELECT * FROM orders WHERE status = 'paid'");
}
public function findOrderById(int $id): float {
$order = $this->db->fetchOrder($id);
// Psalm Error: PossiblyNullReference
return $order->calculateTotal();
}
public function getLegacyMeta(int $id) {
$order = $this->db->fetchOrder($id);
// Psalm Error: UndefinedPropertyFetch (but we know this exists in the DB)
return $order->legacy_meta_field;
}
}
There are no comments for now.