Skip to main content

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:

bash
composer create-project symfony/skeleton my-symfony-project

For a more feature-complete starter project with a web interface, use:

bash
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.

php
// 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:

php
// 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:

bash
# 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:

bash
php bin/console make:controller ProductController

This will create a new controller class in src/Controller/ProductController.php:

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:

php
// 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

twig
{# 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

php
// 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

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\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

php
// 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

twig
{# 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

php
// 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

php
// 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

php
// 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

php
// 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

php
// src/Entity/Product.php
// (See the Doctrine ORM section above for the full entity)

Controller

php
// 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

twig
{# 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 %}
twig
{# 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:

  1. Use bundles for reusable code: Create bundles to encapsulate functionality that can be reused across projects.
  2. Follow naming conventions: Use camelCase for methods, PascalCase for classes, and snake_case for configuration files.
  3. Keep controllers thin: Controllers should handle the HTTP request and response, delegating business logic to services.
  4. Use dependency injection: Inject dependencies through constructor arguments instead of creating them inside your classes.
  5. Write tests: Use PHPUnit to test your application's functionality.
  6. Configure appropriately: Use environment variables for configuration that changes between environments.
  7. Use Doctrine repositories: Create repository methods for complex database queries.
  8. 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

Exercises

  1. Create a blog application: Build a simple blog with posts and comments using Symfony.
  2. Add authentication: Implement user registration and login using Symfony Security.
  3. Create an API: Build a RESTful API using Symfony and API Platform.
  4. Implement file uploads: Add file upload functionality to your product catalog.
  5. 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! :)