Skip to main content

Domain driven design

· 3 min read
Muneer shafi
Senior software engineer @ qbiltrade

This document explains how to design and implement a Product and Group relationship using the principles of Domain-Driven Design (DDD) in a Symfony application. The example demonstrates creating a Product object while adhering to DDD principles.


Key DDD Concepts

1. Aggregate Root

  • The Group entity is the Aggregate Root in this scenario.
  • A Product is part of the Group aggregate and cannot exist independently.
  • All operations on Product objects must go through the Group entity.

2. Encapsulation of Logic

  • Business rules for managing Product objects are encapsulated within the Group entity.
  • Direct instantiation of Product is not allowed outside the Group context.

3. Value Objects (Optional)

  • Value objects can be used for attributes like Price to encapsulate validation and logic.

Implementation Steps

1. Group Entity

The Group entity is responsible for managing its associated Product objects.

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Group
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 255)]
private ?string $name = null;

#[ORM\OneToMany(mappedBy: 'group', targetEntity: Product::class, cascade: ['persist', 'remove'])]
private Collection $products;

public function __construct(string $name)
{
$this->name = $name;
$this->products = new ArrayCollection();
}

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
return $this->name;
}

public function getProducts(): Collection
{
return $this->products;
}

public function addProduct(string $name, float $price, string $description): Product
{
$product = new Product($this, $name, $price, $description);
$this->products->add($product);

return $product;
}
}

2. Product Entity

The Product entity is designed to ensure it is always created within the context of a Group.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 255)]
private ?string $name = null;

#[ORM\Column(type: 'float')]
private ?float $price = null;

#[ORM\Column(type: 'text')]
private ?string $description = null;

#[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'products')]
#[ORM\JoinColumn(nullable: false)]
private ?Group $group = null;

public function __construct(Group $group, string $name, float $price, string $description)
{
$this->group = $group;
$this->name = $name;
$this->price = $price;
$this->description = $description;
}

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
return $this->name;
}

public function getPrice(): ?float
{
return $this->price;
}

public function getDescription(): ?string
{
return $this->description;
}

public function getGroup(): ?Group
{
return $this->group;
}
}

3. Service Layer for Product Creation

All operations involving domain logic should be handled in a service or application layer.

use App\Entity\Group;
use Doctrine\ORM\EntityManagerInterface;

class ProductService
{
private EntityManagerInterface $entityManager;

public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}

public function createProductForGroup(int $groupId, string $name, float $price, string $description): void
{
// Fetch the Group
$groupRepository = $this->entityManager->getRepository(Group::class);
$group = $groupRepository->find($groupId);

if (!$group) {
throw new \DomainException('Group not found');
}

// Add Product via Group
$group->addProduct($name, $price, $description);

// Persist changes
$this->entityManager->persist($group); // Products are persisted via cascade
$this->entityManager->flush();
}
}

4. Advantages of This Design

  • Encapsulation: Business logic for creating Product objects is centralized in the Group entity.
  • Aggregate Consistency: Ensures Product objects are always created within a valid Group.
  • Validation: Business rules for Product creation can be enforced in the addProduct method of the Group entity.

Example Usage

$productService = new ProductService($entityManager);
$productService->createProductForGroup(1, 'Sample Product', 100.0, 'Sample Description');

Conclusion

By following the principles of DDD, this approach ensures:

  • Clear separation of concerns.
  • Consistent and valid domain models.
  • Adherence to the Aggregate Root pattern, making the domain more maintainable and expressive.

Orchestration Platform

· 4 min read
Muneer shafi
Senior software engineer @ qbiltrade

Table of Contents

1. Introduction

Container orchestration automates the deployment, management, scaling, and networking of containers. It's essential for managing containerized applications in production environments.

Core Benefits

  • Automated container lifecycle management
  • Efficient resource utilization
  • High availability and fault tolerance
  • Simplified scaling operations
  • Streamlined deployment processes

2. Key Concepts

2.1 Container Management

  • Container lifecycle
  • Image management
  • Container networking
  • Storage management
  • Resource allocation

2.2 Orchestration Fundamentals

  • Scheduling
  • Load balancing
  • Service discovery
  • Health monitoring
  • Rolling updates

3. Major Platforms

3.1 Kubernetes (K8s)

  • Architecture

    • Control Plane Components
      • API Server
      • etcd
      • Scheduler
      • Controller Manager
    • Node Components
      • Kubelet
      • Container Runtime
      • Kube Proxy
  • Key Features

    • Automated rollouts/rollbacks
    • Service discovery and load balancing
    • Storage orchestration
    • Self-healing capabilities
    • Batch execution

3.2 Docker Swarm

  • Architecture

    • Manager Nodes
    • Worker Nodes
    • Services
    • Tasks
  • Key Features

    • Native Docker integration
    • Built-in security
    • Load balancing
    • Service scaling
    • Rolling updates

3.3 Amazon ECS

  • Architecture

    • Control Plane
    • Data Plane
    • Task Definitions
    • Services
  • Key Features

    • AWS integration
    • Fargate support
    • Auto-scaling
    • Load balancing
    • Container instance management

4. Architecture Components

4.1 Control Plane

  • Functions

    • Cluster management
    • Scheduling decisions
    • Controller operations
    • API management
  • Components

    • Configuration store
    • Scheduler
    • Controllers
    • API interface

4.2 Data Plane

  • Functions

    • Container runtime
    • Networking
    • Storage
    • Monitoring
  • Components

    • Container engine
    • Network plugin
    • Storage plugin
    • Monitoring agent

5. Features Comparison

5.1 Scalability

PlatformMax NodesAuto-scalingLoad Balancing
Kubernetes5000+YesAdvanced
Docker Swarm1000+BasicBuilt-in
Amazon ECSAWS LimitsYesELB Integration

5.2 Management Features

FeatureKubernetesDocker SwarmAmazon ECS
GUI DashboardYesLimitedYes
CLI ToolsExtensiveBasicAWS CLI
APIComprehensiveBasicAWS API

6. Best Practices

6.1 Deployment Strategy

  • Use infrastructure as code
  • Implement CI/CD pipelines
  • Follow immutable infrastructure principles
  • Use proper tagging and versioning
  • Implement blue-green deployments

6.2 Security Practices

  • Enable RBAC
  • Use network policies
  • Implement secrets management
  • Regular security updates
  • Container image scanning

6.3 Resource Management

  • Set resource limits
  • Use namespaces
  • Implement quotas
  • Monitor resource usage
  • Configure auto-scaling

7. Implementation Guidelines

7.1 Initial Setup

# Kubernetes Cluster Setup
kubectl create namespace production
kubectl apply -f configuration.yaml
kubectl apply -f deployment.yaml

# Docker Swarm Setup
docker swarm init
docker stack deploy -c docker-compose.yml myapp

7.2 Configuration Management

# Example Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0

8. Security Considerations

8.1 Network Security

  • Network policies
  • Service mesh implementation
  • TLS encryption
  • API security
  • Container network isolation

8.2 Access Control

  • RBAC configuration
  • Service accounts
  • Secret management
  • Certificate management
  • Audit logging

9. Monitoring and Logging

9.1 Monitoring Tools

  • Prometheus
  • Grafana
  • CloudWatch
  • Datadog
  • New Relic

9.2 Logging Solutions

  • ELK Stack
  • Fluentd
  • Splunk
  • CloudWatch Logs
  • Loki

10. Troubleshooting

10.1 Common Issues

  • Container startup failures
  • Network connectivity issues
  • Resource constraints
  • Configuration errors
  • Service discovery problems

10.2 Debugging Commands

# Kubernetes
kubectl describe pod <pod-name>
kubectl logs <pod-name>
kubectl get events

# Docker Swarm
docker service logs <service-name>
docker service inspect <service-name>
docker node ls

Conclusion

Choosing the right container orchestration platform depends on:

  • Scale requirements
  • Technical expertise
  • Infrastructure requirements
  • Budget constraints
  • Integration needs

Each platform has its strengths and ideal use cases. Careful evaluation of requirements and resources is essential for successful implementation.

Dependency Injection in Symfony

· 6 min read
Muneer shafi
Senior software engineer @ qbiltrade

Table of Contents

Understanding Dependency Injection

What is Dependency Injection?

Dependency Injection (DI) is a design pattern that implements Inversion of Control (IoC) for resolving dependencies. Instead of creating objects directly within a class, dependencies are "injected" into the class from the outside.

Benefits

  • Improved modularity
  • Easier unit testing
  • More maintainable code
  • Reduced coupling between classes
  • Better separation of concerns
  • Enhanced code reusability

Core Concepts

1. Service

A service is any PHP object that performs a specific task. Examples include:

  • Database connections
  • Mailers
  • Logger services
  • Custom business logic classes

2. Service Container

The service container, also known as DI container, is responsible for:

  • Managing service instantiation
  • Resolving dependencies
  • Managing service lifecycle

3. Configuration

Services are configured using:

  • YAML files
  • XML files
  • PHP configuration
  • Attributes/Annotations

Types of Dependency Injection

1. Constructor Injection

// Most recommended approach
class UserService
{
private EmailService $emailService;
private LoggerInterface $logger;

public function __construct(
EmailService $emailService,
LoggerInterface $logger
) {
$this->emailService = $emailService;
$this->logger = $logger;
}
}

2. Setter Injection

class UserService
{
private EmailService $emailService;

public function setEmailService(EmailService $emailService): void
{
$this->emailService = $emailService;
}
}

3. Property Injection

class UserService
{
#[Inject]
private EmailService $emailService;
}

Symfony Service Container

Basic Service Configuration (services.yaml)

services:
# Default configuration
_defaults:
autowire: true
autoconfigure: true
public: false

# Registers services in src/ directory
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}'

Manual Service Configuration

services:
App\Service\CustomService:
arguments:
$apiKey: '%env(API_KEY)%'
$logger: '@logger'
calls:
- setMailer: ['@mailer']
tags: ['app.custom_service']

Practical Examples

1. Basic Service Class

namespace App\Service;

class NewsletterManager
{
public function __construct(
private readonly MailerInterface $mailer,
private readonly LoggerInterface $logger,
private readonly string $sender
) {}

public function send(string $subject, string $content, array $recipients): void
{
try {
$email = (new Email())
->from($this->sender)
->subject($subject)
->text($content);

foreach ($recipients as $recipient) {
$email->addTo($recipient);
}

$this->mailer->send($email);
$this->logger->info('Newsletter sent successfully');
} catch (\Exception $e) {
$this->logger->error('Failed to send newsletter: ' . $e->getMessage());
throw $e;
}
}
}

2. Controller Using Services

namespace App\Controller;

use App\Service\NewsletterManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class NewsletterController extends AbstractController
{
public function __construct(
private readonly NewsletterManager $newsletterManager
) {}

#[Route('/send-newsletter', name: 'send_newsletter')]
public function send(): Response
{
$this->newsletterManager->send(
'Weekly Newsletter',
'Newsletter content...',
['user1@example.com', 'user2@example.com']
);

return new Response('Newsletter sent!');
}
}

3. Service with Configuration

namespace App\Service;

class PaymentProcessor
{
public function __construct(
private readonly string $apiKey,
private readonly bool $testMode,
private readonly LoggerInterface $logger
) {}

public function processPayment(float $amount, string $currency): bool
{
$this->logger->info('Processing payment', [
'amount' => $amount,
'currency' => $currency,
'mode' => $this->testMode ? 'test' : 'live'
]);

// Payment processing logic...
return true;
}
}

Configuration in services.yaml:

services:
App\Service\PaymentProcessor:
arguments:
$apiKey: '%env(PAYMENT_API_KEY)%'
$testMode: '%env(bool:PAYMENT_TEST_MODE)%'

Best Practices

1. Constructor Injection

  • Prefer constructor injection over other types
  • Makes dependencies explicit
  • Ensures required dependencies are provided

2. Interface Injection

interface MessageSenderInterface
{
public function send(string $message): void;
}

class EmailSender implements MessageSenderInterface
{
public function send(string $message): void
{
// Send email implementation
}
}

class SMSSender implements MessageSenderInterface
{
public function send(string $message): void
{
// Send SMS implementation
}
}

class NotificationService
{
public function __construct(
private readonly MessageSenderInterface $sender
) {}

public function notify(string $message): void
{
$this->sender->send($message);
}
}

3. Service Configuration

  • Use autowiring when possible
  • Configure services as private by default
  • Use interfaces for type-hinting
  • Use environment variables for configuration

Advanced Concepts

1. Tagged Services

interface HandlerInterface
{
public function handle(mixed $data): void;
}

#[AsTaggedItem('app.handler')]
class LogHandler implements HandlerInterface
{
public function handle(mixed $data): void
{
// Log handling logic
}
}

class HandlerManager
{
/**
* @param iterable<HandlerInterface> $handlers
*/
public function __construct(
#[TaggedIterator('app.handler')]
private readonly iterable $handlers
) {}

public function process(mixed $data): void
{
foreach ($this->handlers as $handler) {
$handler->handle($data);
}
}
}

2. Factory Services

namespace App\Factory;

class PaymentProcessorFactory
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly string $apiKey
) {}

public function create(string $type): PaymentProcessorInterface
{
return match ($type) {
'stripe' => new StripeProcessor($this->apiKey, $this->logger),
'paypal' => new PayPalProcessor($this->apiKey, $this->logger),
default => throw new \InvalidArgumentException('Invalid processor type')
};
}
}

3. Compiler Passes

namespace App\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class HandlerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has(HandlerManager::class)) {
return;
}

$definition = $container->findDefinition(HandlerManager::class);
$handlers = [];

foreach ($container->findTaggedServiceIds('app.handler') as $id => $tags) {
$handlers[] = new Reference($id);
}

$definition->setArgument('$handlers', $handlers);
}
}

Common Pitfalls and Solutions

1. Circular Dependencies

  • Avoid circular dependencies between services
  • Use service locator pattern if necessary
  • Consider redesigning the architecture

2. Service Scope

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(tags: ['controller.service_arguments'])]
class ScopedService implements ServiceSubscriberInterface
{
public static function getSubscribedServices(): array
{
return [
'request_stack' => RequestStack::class,
];
}
}

3. Performance Optimization

  • Use compiled container in production
  • Configure service sharing appropriately
  • Use lazy loading for heavy services
services:
App\Service\HeavyService:
lazy: true

Testing with Dependency Injection

1. Unit Testing

namespace App\Tests\Service;

use PHPUnit\Framework\TestCase;

class NewsletterManagerTest extends TestCase
{
private NewsletterManager $manager;
private MockObject $mailer;
private MockObject $logger;

protected function setUp(): void
{
$this->mailer = $this->createMock(MailerInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->manager = new NewsletterManager(
$this->mailer,
$this->logger,
'sender@example.com'
);
}

public function testSendNewsletter(): void
{
$this->mailer
->expects($this->once())
->method('send');

$this->manager->send(
'Test Subject',
'Test Content',
['recipient@example.com']
);
}
}

2. Integration Testing

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class NewsletterControllerTest extends WebTestCase
{
public function testSendNewsletter(): void
{
$client = static::createClient();

$client->request('GET', '/send-newsletter');

$this->assertResponseIsSuccessful();
}
}

Conclusion

Dependency Injection in Symfony provides a robust foundation for building maintainable and testable applications. By following the principles and practices outlined in this guide, you can create more modular and flexible code that's easier to maintain and test.

Remember to:

  • Use constructor injection when possible
  • Leverage autowiring for simple cases
  • Configure services explicitly when needed
  • Follow Symfony's best practices
  • Write tests for your services

GitHub Actions

· 3 min read
Muneer shafi
Senior software engineer @ qbiltrade

Introduction

GitHub Actions is a powerful platform designed to automate developer workflows, extending far beyond just CI/CD capabilities. This guide covers the fundamental concepts, practical applications, and hands-on implementation of GitHub Actions.

What is GitHub Actions?

GitHub Actions is an automation platform that enables developers to:

  • Automate software development workflows
  • Respond to events within GitHub repositories
  • Execute custom actions based on triggers
  • Streamline development processes
  • Integrate with various tools and services

Key Concepts

Events

Events are specific activities that trigger a workflow, such as:

  • Push to a branch
  • Pull request creation
  • Issue creation
  • Repository events
  • External triggers

Actions

Actions are individual tasks that form part of a workflow:

  • Pre-built actions from the GitHub Marketplace
  • Custom actions created by developers
  • Community-maintained actions
  • Simple command-line operations

Workflows

Workflows are automated procedures that:

  • Combine multiple actions
  • Execute in response to events
  • Run on GitHub-hosted runners
  • Can be customized for different environments

CI/CD Pipeline Benefits

Integration Advantages

  • Native GitHub integration
  • No additional third-party tools required
  • Simplified setup process
  • Developer-friendly configuration

Environment Features

  • Pre-configured runners with common tools
  • Support for multiple operating systems (Ubuntu, Windows, MacOS)
  • Easy integration with cloud services
  • Built-in secret management

Workflow File Structure

Basic Components

name: Workflow Name
on:
[events that trigger the workflow]
jobs:
job_name:
runs-on: [operating system]
steps:
- name: Step Name
uses: action/repository@version
with:
parameter: value

Key Elements

  • Workflow name (descriptive identifier)
  • Trigger events (push, pull_request, etc.)
  • Jobs (grouped sets of steps)
  • Runner specification (OS choice)
  • Steps (individual actions or commands)

Best Practices

Workflow Organization

  • Use descriptive names for workflows
  • Group related actions into jobs
  • Leverage existing actions from marketplace
  • Implement proper secret management

Security Considerations

  • Store sensitive data in GitHub Secrets
  • Use specific versions for actions
  • Implement proper access controls
  • Regular workflow maintenance

Common Use Cases

Repository Management

  • Automated issue labeling
  • Pull request reviews
  • Contributor management
  • Release automation

Development Operations

  • Code building and testing
  • Docker image creation and publishing
  • Artifact deployment
  • Environment provisioning

Execution Environment

Runners

  • GitHub-hosted runners (Ubuntu, Windows, MacOS)
  • Self-hosted runners for custom environments
  • Parallel job execution capabilities
  • Isolated execution environments

Resource Management

  • Each job runs in a fresh environment
  • Automatic cleanup after execution
  • Built-in caching mechanisms
  • Resource optimization

Advanced Features

Matrix Builds

  • Test across multiple operating systems
  • Support for various language versions
  • Parallel execution of test configurations
  • Custom variable matrices

Workflow Control

  • Conditional execution
  • Job dependencies
  • Environment variables
  • Custom scripts and commands

Conclusion

GitHub Actions provides a robust platform for automation that goes beyond traditional CI/CD tools. Its tight integration with GitHub, extensive marketplace, and flexible configuration options make it an excellent choice for development teams looking to streamline their workflows.

Symfony Framework

· 5 min read
Muneer shafi
Senior software engineer @ qbiltrade

Table of Contents

Introduction

Symfony is a PHP framework for web applications that follows the MVC pattern. It's built with components that can be used independently and provides a robust foundation for complex applications.

Top 10 Features

1. Dependency Injection Container

The DI container manages services and dependencies efficiently.

// services.yaml
services:
App\Service\EmailService:
arguments:
$mailer: '@mailer'
$logger: '@logger'

// Using the service
class EmailController
{
public function __construct(
private readonly EmailService $emailService
) {}

#[Route('/send-email', name: 'send_email')]
public function send(): Response
{
$this->emailService->sendWelcomeEmail();
return new Response('Email sent!');
}
}

2. Routing System

Flexible routing with attributes, annotations, or YAML configuration.

// Using attributes
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// Fetch blog post using slug
return $this->render('blog/show.html.twig', [
'slug' => $slug
]);
}

// Using YAML
# config/routes.yaml
blog_list:
path: /blog
controller: App\Controller\BlogController::list

3. Form Component

Powerful form creation and handling system.

class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class)
->add('email', EmailType::class)
->add('message', TextareaType::class)
->add('submit', SubmitType::class);
}
}

// In controller
public function contact(Request $request): Response
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// Process form data
return $this->redirectToRoute('contact_success');
}

return $this->render('contact/form.html.twig', [
'form' => $form->createView()
]);
}

4. Twig Template Engine

Powerful and flexible template system.

{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>

{# templates/blog/show.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}{{ post.title }}{% endblock %}

{% block body %}
<h1>{{ post.title }}</h1>
<div class="content">
{{ post.content|markdown }}
</div>
{% endblock %}

5. Security Component

Comprehensive security system with authentication and authorization.

// security.yaml
security:
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email

firewalls:
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
logout:
path: app_logout

// SecurityController
#[Route('/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();

return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}

6. Doctrine ORM Integration

Seamless database interaction with Doctrine ORM.

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 255)]
private ?string $name = null;

#[ORM\Column]
private ?float $price = null;
}

// Using in controller
class ProductController extends AbstractController
{
#[Route('/products', name: 'product_list')]
public function list(ProductRepository $repository): Response
{
$products = $repository->findAll();
return $this->render('product/list.html.twig', [
'products' => $products
]);
}
}

7. Console Component

Command-line interface for tasks and commands.

#[AsCommand(
name: 'app:create-user',
description: 'Creates a new user'
)]
class CreateUserCommand extends Command
{
public function __construct(
private readonly UserManager $userManager,
private readonly ValidatorInterface $validator
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
// Command logic
$this->userManager->createUser($input->getArgument('email'));
return Command::SUCCESS;
}
}

8. Event Dispatcher

Powerful event system for application-wide communication.

class UserRegisteredEvent extends Event
{
public function __construct(private readonly User $user)
{}

public function getUser(): User
{
return $this->user;
}
}

class EmailNotifier implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UserRegisteredEvent::class => 'onUserRegistered'
];
}

public function onUserRegistered(UserRegisteredEvent $event): void
{
// Send welcome email
}
}

9. Cache Component

Advanced caching system with multiple backends.

class ProductController
{
public function __construct(
private readonly CacheInterface $cache
) {}

#[Route('/product/{id}', name: 'product_show')]
public function show(int $id): Response
{
$cacheKey = 'product_' . $id;

return $this->cache->get($cacheKey, function(ItemInterface $item) use ($id) {
$item->expiresAfter(3600);
return $this->productRepository->find($id);
});
}
}

10. HTTP Foundation

Object-oriented layer for HTTP interaction.

class ApiController extends AbstractController
{
#[Route('/api/data', name: 'api_data', methods: ['POST'])]
public function handleData(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);

return $this->json([
'status' => 'success',
'data' => $data
]);
}
}

Framework Architecture

Directory Structure

my-project/
├── config/
├── public/
├── src/
│ ├── Controller/
│ ├── Entity/
│ ├── Repository/
│ ├── Service/
│ └── Kernel.php
├── templates/
├── tests/
├── translations/
├── var/
└── vendor/

MVC Pattern Implementation

// Entity (Model)
#[ORM\Entity]
class Article
{
#[ORM\Column(type: 'string')]
private string $title;

#[ORM\Column(type: 'text')]
private string $content;
}

// Controller
class ArticleController extends AbstractController
{
#[Route('/article/{id}', name: 'article_show')]
public function show(Article $article): Response
{
return $this->render('article/show.html.twig', [
'article' => $article
]);
}
}

// View (Twig template)
{# templates/article/show.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<h1>{{ article.title }}</h1>
<div>{{ article.content }}</div>
{% endblock %}

Best Practices

1. Service Organization

// Organize services by domain
namespace App\Service\Payment;

class StripeService
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly string $apiKey
) {}
}

2. Controller Best Practices

class ProductController extends AbstractController
{
public function __construct(
private readonly ProductService $productService,
private readonly FormFactoryInterface $formFactory
) {}

#[Route('/product/new', name: 'product_new')]
public function new(Request $request): Response
{
// Thin controller, business logic in service
$product = new Product();
$form = $this->formFactory->create(ProductType::class, $product);

if ($this->productService->handleNewProduct($form, $request)) {
return $this->redirectToRoute('product_list');
}

return $this->render('product/new.html.twig', [
'form' => $form->createView()
]);
}
}

3. Configuration Management

# config/packages/app.yaml
parameters:
app.feature_flags:
new_ui: true
api_version: 2

services:
App\Service\FeatureFlag:
arguments:
$flags: '%app.feature_flags%'

Conclusion

Symfony provides a robust foundation for building web applications with its comprehensive feature set and well-organized architecture. The framework's emphasis on best practices, reusability, and maintainability makes it an excellent choice for complex applications.

Key takeaways:

  • Strong dependency injection system
  • Comprehensive security features
  • Powerful routing and form handling
  • Excellent database integration
  • Extensive ecosystem of bundles and components