PHP Symfony Basics
Introduction
Symfony is a powerful, high-performance PHP framework designed for creating web applications. It follows the Model-View-Controller (MVC) architectural pattern and provides a set of reusable components that help developers build robust applications quickly and efficiently. Whether you're creating a simple website or a complex enterprise application, Symfony offers the flexibility and tools you need.
This guide will introduce you to the core concepts of Symfony, walk you through installation, and help you build your first Symfony application. By the end, you'll have a solid understanding of how Symfony works and why it's one of the most popular PHP frameworks in use today.
Why Use Symfony?
Before diving into Symfony's features, let's understand why you might choose Symfony for your next PHP project:
- Reusable Components: Symfony components can be used independently in any PHP project
- Mature Ecosystem: A stable framework with excellent documentation and a large community
- Scalability: Designed to handle everything from small websites to enterprise applications
- Performance: Optimized for speed with built-in caching mechanisms
- Flexibility: Adaptable to your specific project requirements
- Testing Support: Built with testability in mind
- Security: Strong security features to protect your applications
Symfony Architecture
Symfony follows the Model-View-Controller (MVC) architectural pattern, which separates your application into three main components:
- Model: Manages the data, logic, and rules of the application
- View: Renders the data as HTML, XML, JSON, etc.
- Controller: Handles user input and converts it to commands for the model or view
Installation and Setup
Prerequisites
Before installing Symfony, ensure you have:
- PHP 8.1 or higher
- Composer (dependency manager for PHP)
- A web server like Apache or Nginx (optional for development)
Installation
The easiest way to create a new Symfony project is using Composer. Open your terminal and run:
composer create-project symfony/skeleton my-symfony-project
For a more feature-complete starter project with a web interface, use:
composer create-project symfony/website-skeleton my-symfony-project
Project Structure
After installation, you'll find a directory structure similar to this:
my-symfony-project/
├── bin/
├── config/
├── public/
├── src/
├── templates/
├── tests/
├── translations/
├── var/
└── vendor/
Key directories include:
- bin/: Contains executable files like the Symfony console
- config/: Application configuration files
- public/: Web accessible files, including index.php
- src/: Your PHP code (controllers, entities, services)
- templates/: Twig templates for rendering views
- var/: Generated files (cache, logs)
- vendor/: Third-party dependencies
The Symfony Kernel and HTTP Foundation
At the heart of every Symfony application is the Kernel
class. This class bootstraps the application and handles the request-response cycle.
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
The HTTP Foundation component provides an object-oriented layer for HTTP requests and responses:
// src/Controller/HomeController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController
{
#[Route('/', name: 'home')]
public function index(): Response
{
return new Response('Hello, World!');
}
}
The Symfony Console
Symfony comes with a powerful command-line tool called the Symfony Console. You can use it to run various commands that help with development, such as:
# List all available commands
php bin/console
# Start a development server
php bin/console server:start
# Clear the cache
php bin/console cache:clear
# Create a new controller
php bin/console make:controller HomeController
Controllers and Routing
Controllers in Symfony handle HTTP requests and return responses. They define the actions that can be performed within your application.
Creating a Controller
You can create a controller manually or use the Symfony console:
php bin/console make:controller ProductController
This will create a new controller class in src/Controller/ProductController.php
:
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ProductController extends AbstractController
{
#[Route('/product', name: 'app_product')]
public function index(): Response
{
return $this->render('product/index.html.twig', [
'controller_name' => 'ProductController',
]);
}
}
Routes and Annotations
Routes in Symfony map URLs to controller actions. The most common way to define routes is using attributes (PHP 8+) or annotations:
// Using PHP 8 attributes
#[Route('/product/{id}', name: 'product_show', methods: ['GET'])]
public function show(int $id): Response
{
// ...
}
// Using annotations (for PHP < 8)
/**
* @Route("/product/{id}", name="product_show", methods={"GET"})
*/
public function show(int $id): Response
{
// ...
}
You can also define routes in YAML, XML, or PHP files in the config/routes
directory.
Twig Templates
Symfony uses Twig as its default templating engine. Twig provides a clean and powerful syntax for rendering HTML.
Basic Template
{# templates/product/show.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Product Details{% endblock %}
{% block body %}
<h1>{{ product.name }}</h1>
<p>Price: ${{ product.price }}</p>
{% if product.inStock %}
<span class="badge badge-success">In Stock</span>
{% else %}
<span class="badge badge-danger">Out of Stock</span>
{% endif %}
<a href="{{ path('product_list') }}">Back to list</a>
{% endblock %}
Using Templates in Controllers
// src/Controller/ProductController.php
#[Route('/product/{id}', name: 'product_show')]
public function show(int $id): Response
{
$product = [
'id' => $id,
'name' => 'Smartphone XYZ',
'price' => 499.99,
'inStock' => true
];
return $this->render('product/show.html.twig', [
'product' => $product
]);
}
Forms
Symfony provides a powerful form component that helps you handle forms with ease.
Creating a Form
// 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\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class)
->add('description', TextType::class)
->add('price', MoneyType::class, [
'currency' => 'USD',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Product::class,
]);
}
}
Using Forms in Controllers
// src/Controller/ProductController.php
#[Route('/product/new', name: 'product_new')]
public function new(Request $request): Response
{
$product = new Product();
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Save the product to the database
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
return $this->redirectToRoute('product_show', [
'id' => $product->getId()
]);
}
return $this->render('product/new.html.twig', [
'form' => $form->createView()
]);
}
Rendering Forms in Templates
{# templates/product/new.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}New Product{% endblock %}
{% block body %}
<h1>Create New Product</h1>
{{ form_start(form) }}
{{ form_row(form.name) }}
{{ form_row(form.description) }}
{{ form_row(form.price) }}
<button type="submit" class="btn btn-primary">Create</button>
{{ form_end(form) }}
{% endblock %}
Doctrine ORM
Symfony uses Doctrine as its default Object-Relational Mapper (ORM), which allows you to work with databases using PHP objects.
Entity Definition
// src/Entity/Product.php
namespace App\Entity;
use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as 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(length: 1000, nullable: true)]
private ?string $description = null;
#[ORM\Column]
private ?float $price = null;
// Getters and setters
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(float $price): self
{
$this->price = $price;
return $this;
}
}
Database Configuration
Configure your database connection in .env
:
# .env
DATABASE_URL="mysql://db_user:[email protected]:3306/db_name?serverVersion=8.0"
Working with Entities
// src/Controller/ProductController.php
#[Route('/products', name: 'product_list')]
public function list(ProductRepository $productRepository): Response
{
$products = $productRepository->findAll();
return $this->render('product/list.html.twig', [
'products' => $products
]);
}
Services and Dependency Injection
Symfony leverages a powerful Dependency Injection Container to manage services.
Creating a Service
// src/Service/ProductService.php
namespace App\Service;
use App\Repository\ProductRepository;
class ProductService
{
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getDiscountedProducts(float $discountPercentage): array
{
$products = $this->productRepository->findAll();
$discountedProducts = [];
foreach ($products as $product) {
$discountedPrice = $product->getPrice() * (1 - $discountPercentage / 100);
$discountedProducts[] = [
'product' => $product,
'discountedPrice' => $discountedPrice
];
}
return $discountedProducts;
}
}
Using Services in Controllers
// src/Controller/ProductController.php
#[Route('/products/discounted', name: 'product_discounted')]
public function discounted(ProductService $productService): Response
{
$discountedProducts = $productService->getDiscountedProducts(20); // 20% discount
return $this->render('product/discounted.html.twig', [
'discountedProducts' => $discountedProducts
]);
}
Building a Complete Example: Product Catalog
Let's build a simple product catalog to demonstrate how these concepts work together.
Entity
// src/Entity/Product.php
// (See the Doctrine ORM section above for the full entity)
Controller
// 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\Annotation\Route;
#[Route('/product')]
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();
return $this->redirectToRoute('product_index');
}
return $this->render('product/new.html.twig', [
'product' => $product,
'form' => $form->createView(),
]);
}
#[Route('/{id}', name: 'product_show', methods: ['GET'])]
public function show(Product $product): Response
{
return $this->render('product/show.html.twig', [
'product' => $product,
]);
}
#[Route('/{id}/edit', name: 'product_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Product $product, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('product_index');
}
return $this->render('product/edit.html.twig', [
'product' => $product,
'form' => $form->createView(),
]);
}
#[Route('/{id}', name: 'product_delete', methods: ['POST'])]
public function delete(Request $request, Product $product, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$product->getId(), $request->request->get('_token'))) {
$entityManager->remove($product);
$entityManager->flush();
}
return $this->redirectToRoute('product_index');
}
}
Templates
{# templates/product/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Product List{% endblock %}
{% block body %}
<h1>Product List</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for product in products %}
<tr>
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>${{ product.price }}</td>
<td>
<a href="{{ path('product_show', {'id': product.id}) }}">show</a>
<a href="{{ path('product_edit', {'id': product.id}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="4">No products found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ path('product_new') }}">Create new</a>
{% endblock %}
{# templates/product/show.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Product Details{% endblock %}
{% block body %}
<h1>{{ product.name }}</h1>
<table class="table">
<tbody>
<tr>
<th>ID</th>
<td>{{ product.id }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ product.name }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ product.description }}</td>
</tr>
<tr>
<th>Price</th>
<td>${{ product.price }}</td>
</tr>
</tbody>
</table>
<a href="{{ path('product_index') }}">Back to list</a>
<a href="{{ path('product_edit', {'id': product.id}) }}">Edit</a>
<form method="post" action="{{ path('product_delete', {'id': product.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ product.id) }}">
<button class="btn btn-danger">Delete</button>
</form>
{% endblock %}
Best Practices
When working with Symfony, follow these best practices:
- Use bundles for reusable code: Create bundles to encapsulate functionality that can be reused across projects.
- Follow naming conventions: Use camelCase for methods, PascalCase for classes, and snake_case for configuration files.
- Keep controllers thin: Controllers should handle the HTTP request and response, delegating business logic to services.
- Use dependency injection: Inject dependencies through constructor arguments instead of creating them inside your classes.
- Write tests: Use PHPUnit to test your application's functionality.
- Configure appropriately: Use environment variables for configuration that changes between environments.
- Use Doctrine repositories: Create repository methods for complex database queries.
- Master the Symfony Console: Use the console to generate code, clear caches, and run tasks.
Summary
In this guide, we've covered the basics of the Symfony PHP framework, including:
- Installation and project structure
- Controllers and routing
- Twig templates
- Forms
- Doctrine ORM
- Services and dependency injection
- A complete example of a product catalog
Symfony's robust architecture and powerful features make it an excellent choice for PHP developers looking to build maintainable web applications. By following the principles and practices outlined in this guide, you'll be well on your way to becoming a proficient Symfony developer.
Additional Resources
- Official Symfony Documentation
- Symfony Best Practices
- SymfonyCasts - Video tutorials on Symfony
- Symfony Community - Forums, Slack, and more
Exercises
- Create a blog application: Build a simple blog with posts and comments using Symfony.
- Add authentication: Implement user registration and login using Symfony Security.
- Create an API: Build a RESTful API using Symfony and API Platform.
- Implement file uploads: Add file upload functionality to your product catalog.
- Add pagination: Implement pagination for your product listing page.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)