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

· 3 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

· 5 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