Symfony architecture in production
Service container, HTTP lifecycle, Doctrine, security and cache: understanding how Symfony assembles the pieces is how you diagnose and evolve an application without breaking it.
Service container and compilation
The container is compiled once into cache: the result is a set of optimized PHP classes, not a dynamic resolution per request. Each service has visibility (`private` by default), a lifecycle and dependencies resolved by autowiring. To diagnose a problem, `debug:container` and the container dump are more reliable than intuition: they show the definition actually used.
# config/services.yaml
services:
App\Quiz\Service\QuizAnswerService:
arguments:
$maxAttempts: '%env(int:QUIZ_MAX_ATTEMPTS)%'The Kernel HTTP cycle
Each request flows through ordered events: `kernel.request`, controller resolution, `kernel.controller`, then `kernel.response`. Listeners let you decorate this cycle without duplicating logic in every controller. Mastering listener priorities and event propagation avoids hard-to-reproduce side effects, especially on error responses and negotiated formats.
final class CorsListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [KernelEvents::RESPONSE => ['onKernelResponse', 10]];
}
public function onKernelResponse(ResponseEvent $event): void
{
$event->getResponse()->headers->set('X-Content-Type-Options', 'nosniff');
}
}Doctrine: identity map, flush and performance
The EntityManager holds an identity map: reading the same entity twice in one request returns the same instance, which guarantees consistency but hides changes made by other connections. `flush()` synchronizes all pending modifications; calling `flush()` in a loop multiplies round trips. For large reads, prefer read-only queries (`HYDRATE_ARRAY` or cursors) over loading full entities.
$rows = $entityManager->createQuery(
'SELECT u.id, u.email FROM App\Account\Entity\User u WHERE u.active = :active'
)
->setParameter('active', true)
->setMaxResults(1000)
->getArrayResult();Security: authentication, authorization and voters
Symfony security separates authentication (who are you?) from authorization (are you allowed?). Voters encode business rules per action and per resource; they are unit-testable without an HTTP request. For resources owned by a user, the voter must load the entity and check ownership, never trust an identifier passed in the URL.
final class QuizVoter extends Voter
{
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, ['view', 'delete'], true) && $subject instanceof QuizSession;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
return $subject->getOwner() === $token->getUser();
}
}