Services and dependency injection
A service is an object that does useful work: sends email, writes to the log, calculates prices. All services live in the container, and Symfony passes them wherever they are requested by type. The whole framework is built this way: EntityManagerInterface and the repositories from earlier lessons are services too.
Your own service
Any class in the src folder is already a service — it needs no registration:
// src/Service/PriceCalculator.php
namespace App\Service;
class PriceCalculator
{
public function withTax(float $price): float
{
return round($price * 1.2, 2);
}
}Getting a service
Just name the type in the arguments of a controller method or in a constructor:
use App\Service\PriceCalculator;
#[Route('/cart', name: 'cart')]
public function cart(PriceCalculator $calculator): Response
{
return new Response('Total: ' . $calculator->withTax(1000));
}A service's dependencies
A service receives other services through its constructor:
namespace App\Service;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\MailerInterface;
class OrderNotifier
{
public function __construct(
private MailerInterface $mailer,
private LoggerInterface $logger,
) {}
public function notify(Order $order): void
{
$this->logger->info('Order placed', ['id' => $order->getId()]);
// sending the email
}
}This is called autowiring: Symfony looks at the types and supplies the objects.
Which services exist
php bin/console debug:autowiring
php bin/console debug:autowiring mailerThe command shows the types you can ask for.
Settings and environment variables
A string or a number cannot be supplied by type — they are named with the #[Autowire] attribute:
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class PaymentGateway
{
public function __construct(
#[Autowire(env: 'STRIPE_SECRET')]
private string $secret,
#[Autowire('%kernel.project_dir%/var/invoices')]
private string $invoiceDir,
) {}
}Environment variables are written in .env, and the secret values of your own machine in .env.local, which does not go into the repository.
An interface and its implementation
If one class implements an interface, Symfony supplies it by itself. When there are several implementations, the right one is chosen in config/services.yaml:
services:
App\Payment\PaymentGatewayInterface: '@App\Payment\StripeGateway'Common mistake
A service is created once and reused. If its properties hold data of a particular request — the current user, an accumulated list — that data "leaks" into the next call. A service should keep only dependencies and settings, and receive data through method arguments.