Symfony

High-performance PHP framework for enterprise applications. Modular design with high flexibility, ideal for large-scale applications.

PHPFrameworkBackendMVCDependency InjectionBundlesEnterprise

GitHub Overview

symfony/symfony

The Symfony PHP framework

Stars30,487
Watchers1,062
Forks9,670
Created:January 4, 2010
Language:PHP
License:MIT License

Topics

bundleframeworkhacktoberfestphpphp-frameworksymfonysymfony-bundle

Star History

symfony/symfony Star History
Data as of: 8/13/2025, 01:43 AM

Web Framework

Symfony

Overview

Symfony is a high-performance, robust web application framework written in PHP. It features a rich set of reusable components and modern architecture optimized for enterprise-grade applications, making it ideal for large-scale web application development.

Details

Symfony was developed by Fabien Potencier in 2005 and continues to be actively maintained as an open-source framework. The latest version is 7.3, featuring innovative capabilities such as HTTP Early Hints, AssetMapper component, Scheduler component, and Clock component.

Key architectural features:

  • Component-based Design: Independent, reusable PHP component suite
  • Kernel System: Centralized management of HTTP request lifecycle
  • Bundle System: Modularized functionality and self-contained packages
  • Dependency Injection Container: Automatic service management with autowiring
  • Event-driven Architecture: Event system including kernel.request, kernel.controller
  • MicroKernelTrait: Support for lightweight application development

Symfony provides LTS (Long Term Support) versions and strictly adheres to semantic versioning, ensuring stable enterprise operations. Integration with powerful components like DoctrineORM, Twig, and Swiftmailer enables efficient full-stack development.

Pros and Cons

Pros

  • Enterprise-grade Robustness: Proven stability and performance in large-scale applications
  • Rich Component Suite: Easy feature extension with 50+ independent components
  • Powerful Dependency Injection: Automatic service resolution through autowiring
  • Excellent Debugging Features: Enhanced development efficiency with WebProfiler and Dev toolbar
  • Extensive Ecosystem: Rich bundles, libraries, and third-party tools
  • Long-term Support: 3-year security support through LTS versions
  • Community Support: Active community and abundant learning resources
  • Testing Excellence: Comprehensive test environment with PHPUnit integration

Cons

  • Steep Learning Curve: Difficult for beginners due to numerous concepts and components
  • Performance: Heavier than lightweight frameworks due to feature richness
  • Configuration Complexity: YAML/XML configuration files can become complex
  • Memory Usage: High memory consumption due to large component set
  • Initial Development Slowness: Excessive functionality for small projects
  • Version Compatibility: Breaking changes during major version upgrades

Key Links

Code Examples

Hello World

<?php
// public/index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

require_once '../vendor/autoload.php';

$request = Request::createFromGlobals();

if ('/' === $request->getPathInfo()) {
    $response = new Response('Hello World!');
} elseif ('/hello' === $request->getPathInfo()) {
    $response = new Response('Hello Symfony!');
} else {
    $response = new Response('Not Found', 404);
}

$response->send();

Controllers and Routing

<?php
// src/Controller/HelloController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class HelloController extends AbstractController
{
    #[Route('/', name: 'app_hello')]
    public function index(): Response
    {
        return $this->render('hello/index.html.twig', [
            'message' => 'Hello Symfony!'
        ]);
    }

    #[Route('/hello/{name}', name: 'app_hello_name', methods: ['GET'])]
    public function hello(string $name = 'World'): Response
    {
        return new Response(
            sprintf('Hello %s!', $name)
        );
    }

    #[Route('/api/users', name: 'api_users', methods: ['GET'])]
    public function getUsers(): Response
    {
        $users = [
            ['id' => 1, 'name' => 'John'],
            ['id' => 2, 'name' => 'Jane']
        ];

        return $this->json($users);
    }
}

Twig Templates

{# templates/hello/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Hello Symfony{% endblock %}

{% block body %}
<div class="container">
    <h1>{{ message }}</h1>
    <p>Current time: {{ "now"|date('Y-m-d H:i:s') }}</p>
    
    <ul>
    {% for i in 1..5 %}
        <li>Item {{ i }}</li>
    {% endfor %}
    </ul>
    
    <a href="{{ path('app_hello_name', {'name': 'Symfony'}) }}">
        Go to Hello Symfony page
    </a>
</div>
{% endblock %}

{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Welcome!{% endblock %}</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>

Services and Dependency Injection

<?php
// src/Service/MessageGenerator.php
namespace App\Service;

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }

    public function getHappyMessage(): string
    {
        $messages = [
            'Symfony is awesome!',
            'PHP rocks with Symfony!',
            'Building great apps with Symfony!'
        ];

        $message = $messages[array_rand($messages)];
        $this->logger->info('Happy message generated', ['message' => $message]);

        return $message;
    }
}

// src/Controller/MessageController.php
namespace App\Controller;

use App\Service\MessageGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class MessageController extends AbstractController
{
    #[Route('/message', name: 'app_message')]
    public function message(MessageGenerator $messageGenerator): Response
    {
        $message = $messageGenerator->getHappyMessage();
        
        return $this->render('message/show.html.twig', [
            'message' => $message
        ]);
    }
}

Doctrine Entities and Repositories

<?php
// src/Entity/Product.php
namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
#[ORM\Table(name: 'products')]
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;

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

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

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

    public function setName(string $name): static
    {
        $this->name = $name;
        return $this;
    }

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

    public function setPrice(float $price): static
    {
        $this->price = $price;
        return $this;
    }

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

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

// src/Repository/ProductRepository.php
namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }

    public function findExpensiveProducts(float $minPrice): array
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.price >= :minPrice')
            ->setParameter('minPrice', $minPrice)
            ->orderBy('p.price', 'ASC')
            ->getQuery()
            ->getResult();
    }

    public function findByNamePattern(string $pattern): array
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.name LIKE :pattern')
            ->setParameter('pattern', '%' . $pattern . '%')
            ->getQuery()
            ->getResult();
    }
}

Form Handling and Validation

<?php
// src/Form/ProductType.php
namespace App\Form;

use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Product Name',
                'constraints' => [
                    new Assert\NotBlank(message: 'Product name is required'),
                    new Assert\Length(
                        min: 2,
                        max: 255,
                        minMessage: 'Product name must be at least 2 characters',
                        maxMessage: 'Product name cannot exceed 255 characters'
                    )
                ]
            ])
            ->add('price', MoneyType::class, [
                'label' => 'Price',
                'currency' => 'USD',
                'constraints' => [
                    new Assert\NotBlank(message: 'Price is required'),
                    new Assert\PositiveOrZero(message: 'Price must be 0 or greater')
                ]
            ])
            ->add('description', TextareaType::class, [
                'label' => 'Description',
                'required' => false,
                'attr' => ['rows' => 5]
            ])
            ->add('save', SubmitType::class, [
                'label' => 'Save'
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    }
}

// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Form\ProductType;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/products')]
class ProductController extends AbstractController
{
    #[Route('/', name: 'product_index', methods: ['GET'])]
    public function index(ProductRepository $productRepository): Response
    {
        return $this->render('product/index.html.twig', [
            'products' => $productRepository->findAll(),
        ]);
    }

    #[Route('/new', name: 'product_new', methods: ['GET', 'POST'])]
    public function new(Request $request, EntityManagerInterface $entityManager): Response
    {
        $product = new Product();
        $form = $this->createForm(ProductType::class, $product);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->persist($product);
            $entityManager->flush();

            $this->addFlash('success', 'Product created successfully');
            return $this->redirectToRoute('product_index');
        }

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

    #[Route('/{id}', name: 'product_show', methods: ['GET'])]
    public function show(Product $product): Response
    {
        return $this->render('product/show.html.twig', [
            'product' => $product,
        ]);
    }
}

API Development and Serialization

<?php
// src/Controller/ApiController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

#[Route('/api/products')]
class ApiController extends AbstractController
{
    public function __construct(
        private ProductRepository $productRepository,
        private EntityManagerInterface $entityManager,
        private SerializerInterface $serializer,
        private ValidatorInterface $validator
    ) {
    }

    #[Route('', name: 'api_products_list', methods: ['GET'])]
    public function list(): JsonResponse
    {
        $products = $this->productRepository->findAll();
        
        return $this->json($products, Response::HTTP_OK, [], [
            'groups' => ['product:read']
        ]);
    }

    #[Route('/{id}', name: 'api_product_show', methods: ['GET'])]
    public function show(Product $product): JsonResponse
    {
        return $this->json($product, Response::HTTP_OK, [], [
            'groups' => ['product:read', 'product:detail']
        ]);
    }

    #[Route('', name: 'api_product_create', methods: ['POST'])]
    public function create(Request $request): JsonResponse
    {
        $product = $this->serializer->deserialize(
            $request->getContent(),
            Product::class,
            'json'
        );

        $errors = $this->validator->validate($product);
        if (count($errors) > 0) {
            return $this->json(['errors' => (string) $errors], Response::HTTP_BAD_REQUEST);
        }

        $this->entityManager->persist($product);
        $this->entityManager->flush();

        return $this->json($product, Response::HTTP_CREATED, [], [
            'groups' => ['product:read']
        ]);
    }

    #[Route('/{id}', name: 'api_product_update', methods: ['PUT'])]
    public function update(Product $product, Request $request): JsonResponse
    {
        $this->serializer->deserialize(
            $request->getContent(),
            Product::class,
            'json',
            ['object_to_populate' => $product]
        );

        $errors = $this->validator->validate($product);
        if (count($errors) > 0) {
            return $this->json(['errors' => (string) $errors], Response::HTTP_BAD_REQUEST);
        }

        $this->entityManager->flush();

        return $this->json($product, Response::HTTP_OK, [], [
            'groups' => ['product:read']
        ]);
    }

    #[Route('/{id}', name: 'api_product_delete', methods: ['DELETE'])]
    public function delete(Product $product): JsonResponse
    {
        $this->entityManager->remove($product);
        $this->entityManager->flush();

        return $this->json(null, Response::HTTP_NO_CONTENT);
    }
}