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
132: Symfony Dependency Injection Container
I've seen this exact pattern dozens of times when developers move from plain PHP to Symfony. They understand the concept of a service, but they keep falling back on their old habits of manual instantiation. Take a look at this NewsletterManager class. At first glance, it looks perfectly fine, but it's actually a maintenance nightmare waiting to happen.
// src/Service/NewsletterManager.php
namespace App\Service;
use App\Service\Mailer;
use App\Repository\UserRepository;
class NewsletterManager
{
private $mailer;
private $userRepository;
public function __construct()
{
// The "New" Keyword Trap
$this->mailer = new Mailer();
$this->userRepository = new UserRepository();
}
public function sendToAll()
{
$users = $this->userRepository->findAll();
foreach ($users as $user) {
$this->mailer->send($user->getEmail(), 'Weekly Update!');
}
}
}
The Problem: Hard-Coded Dependencies
The bug here isn't a syntax error—the code will technically run until it hits a wall. The problem is that NewsletterManager is now "tightly coupled" to the specific implementations of Mailer and UserRepository.
Imagine if the Mailer class needs an API key from your .env file to work. Since you're calling new Mailer() inside the constructor, the Mailer is created without any configuration. It's a "naked" object. To fix it, you'd have to pass the API key into the NewsletterManager, and then pass it again into the Mailer. If you have ten different services using the mailer, you're now passing that API key through ten different constructors. It's a mess.
The Fix: Embracing Constructor Injection
In Symfony, we don't create our own dependencies. We ask for them. The Dependency Injection (DI) Container is essentially a giant map that knows how to instantiate every service in your app and what those services need to function.
Here is how we rewrite this to let Symfony's container do the heavy lifting:
// src/Service/NewsletterManager.php
namespace App\Service;
use App\Service\Mailer;
use App\Repository\UserRepository;
class NewsletterManager
{
private $mailer;
private $userRepository;
// We type-hint the dependencies we need
public function __construct(Mailer $mailer, UserRepository $userRepository)
{
$this->mailer = $mailer;
$this->userRepository = $userRepository;
}
public function sendToAll()
{
$users = $this->userRepository->findAll();
foreach ($users as $user) {
$this->mailer->send($user->getEmail(), 'Weekly Update!');
}
}
}
Now, when Symfony sees that NewsletterManager needs a Mailer, it looks into its container, finds the already-configured Mailer instance (with its API keys and settings), and injects it automatically. This is called Autowiring.
Managing Non-Class Dependencies
Sometimes you don't need another class; you need a string, an integer, or a path to a folder. You can't autowire a string because Symfony wouldn't know which string you want. This is where config/services.yaml comes back into play.
If your Mailer needs a specific sender_email from your parameters, you define it like this:
# config/services.yaml
parameters:
app.sender_email: 'noreply@example.com'
services:
# Default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
App\Service\Mailer:
arguments:
$senderEmail: '%app.sender_email%'
By doing this, you've separated your logic (how to send a mail) from your configuration (who is sending the mail). If you decide to change the sender email, you change one line in a YAML file, and every service using the Mailer is updated instantly. I can't stress enough how much this saves you during deployment across different environments (staging vs. production).
📋 Practical Task
Build a Configurable Discount Calculator Service
Your goal is to implement a DiscountCalculator service that is properly integrated into the Symfony DI container. This service should not use the new keyword for any of its dependencies.
- Requirement 1: Create a
DiscountCalculatorservice. It should depend on aProductRepository(you can mock this as a simple class) to fetch product prices. - Requirement 2: The service needs a percentage value (e.g., 20 for 20%) that is NOT hard-coded in the class. Define this value in
services.yamlunderparametersand inject it into the constructor using a named argument. - Requirement 3: Create a simple Controller action that injects the
DiscountCalculatorand returns the discounted price of a dummy product ID. - Requirement 4: Ensure that
autowire: trueis enabled in your service configuration so that theProductRepositoryis handled automatically.
There are no comments for now.