Skip to content

PHP Symfony Basics — Complete Beginner's Guide to the Symfony Framework

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about PHP Symfony Basics. We cover key concepts, practical examples, and best practices to help you master this topic.

Symfony is a high-performance PHP framework built from reusable components that powers enterprise applications, APIs, and content management systems with a modular architecture and strict coding standards. This tutorial covers installing Symfony, understanding bundles and services, defining routes with annotations and attributes, building Twig templates, modeling data with Doctrine ORM and migrations, building forms with validation, and using the Symfony Console for CLI commands.

What You'll Learn

  • Installing Symfony via Composer and understanding the project structure
  • Creating bundles and registering services in the service container
  • Defining routes using PHP 8 attributes and YAML configuration
  • Building templates with Twig including inheritance and filters
  • Using Doctrine ORM for entities, repositories, migrations, and relationships
  • Creating and validating forms with Symfony Form component
  • Writing custom console commands

Why It Matters

Symfony is the foundation of Drupal, eZ Platform, and many enterprise PHP applications. Its component-based design means you can use its libraries (HttpKernel, Routing, Console, Form) in any PHP project, even outside Symfony itself. The framework enforces high code quality standards, has a strong commitment to backward compatibility, and provides tools like the Symfony Profiler that make development and debugging transparent.

Real-World Use

The backend of Durga Antivirus Pro's cloud scanning service is built with Symfony. Its console commands process uploaded files in a job queue, the Doctrine ORM manages scan result storage, and the Symfony Mailer component sends alert notifications to customers. The framework's security component handles API token authentication and role-based access control.

Learning Path

flowchart LR
  A[Laravel Basics] --> B[Symfony Basics\nYou are here]
  B --> C[Security]
  style B fill:#f90,color:#fff

Installing Symfony

Create a new Symfony project:

composer create-project symfony/skeleton my-app
cd my-app

Start the development server:

symfony server:start

Output:

  OK  Web server is using PHP 8.3.0
  OK  Web server is listening on http://127.0.0.1:8000

Or use the PHP built-in server:

php -S 127.0.0.1:8000 -t public/

Understanding Bundles and Services

In Symfony, everything is a service. Services are registered in the service container and wired together using dependency injection. Bundles package related functionality (routing, templating, database).

Open config/services.yaml:

services:
  _defaults:
    autowire: true
    autoconfigure: true

  App\:
    resource: '../src/'
    exclude:
      - '../src/DependencyInjection/'
      - '../src/Entity/'
      - '../src/Kernel.php'

Create a simple service at src/Service/Greeting.php:

<?php
namespace App\Service;

class Greeting
{
  public function greet(string $name): string
  {
    return "Hello, $name! Welcome to Symfony services.";
  }
}

Use the service in a controller:

<?php
namespace App\Controller;

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

class GreetingController extends AbstractController
{
  public function __construct(
    private Greeting $greeting
  ) {}

  #[Route('/hello/{name}', name: 'hello')]
  public function hello(string $name): Response
  {
    $message = $this->greeting->greet($name);
    return new Response($message);
  }
}

Test with curl:

curl http://127.0.0.1:8000/hello/Alice

Output:

Hello, Alice! Welcome to Symfony services.

Routing with Attributes

Symfony supports PHP 8 attributes for routes. The #[Route] attribute can be applied to controllers and actions:

<?php
namespace App\Controller;

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

#[Route('/api/products')]
class ProductController extends AbstractController
{
  #[Route('/', name: 'product_index', methods: ['GET'])]
  public function index(): JsonResponse
  {
    return $this->json([
      ['id' => 1, 'name' => 'Laptop'],
      ['id' => 2, 'name' => 'Phone'],
    ]);
  }

  #[Route('/{id}', name: 'product_show', methods: ['GET'], requirements: ['id' => '\d+'])]
  public function show(int $id): JsonResponse
  {
    return $this->json(['id' => $id, 'name' => "Product $id"]);
  }
}

Twig Templates

Twig is Symfony's templating engine. Create templates/base.html.twig:

<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}Symfony App{% endblock %}</title>
</head>
<body>
  <header>
    <h1>Symfony Demo</h1>
  </header>
  <main>
    {% block body %}{% endblock %}
  </main>
</body>
</html>

Create templates/product/list.html.twig:

{% extends 'base.html.twig' %}

{% block title %}Product List{% endblock %}

{% block body %}
  <h2>Products</h2>
  <ul>
    {% for product in products %}
      <li>{{ product.name }} - ${{ product.price|number_format(2) }}</li>
    {% endfor %}
  </ul>
{% endblock %}

Update the controller to render the template:

<?php
namespace App\Controller;

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

class ProductController extends AbstractController
{
  #[Route('/products', name: 'product_list')]
  public function list(): Response
  {
    $products = [
      ['name' => 'Laptop', 'price' => 999.50],
      ['name' => 'Phone', 'price' => 699.99],
    ];
    return $this->render('product/list.html.twig', ['products' => $products]);
  }
}

Doctrine ORM and Migrations

Doctrine is Symfony's default ORM. Create an entity:

php bin/console make:entity Product

Follow the interactive prompts to add fields. The generated entity at src/Entity/Product.php:

<?php
namespace App\Entity;

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(type: 'decimal', precision: 8, scale: 2)]
  private ?float $price = null;

  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; }
}

Configure the database in .env:

DATABASE_URL="mysql://root:password@127.0.0.1:3306/myapp"

Create the database and run migrations:

php bin/console doctrine:database:create
php bin/console make:migration
php bin/console doctrine:migrations:migrate

Use the Repository in a controller:

<?php
namespace App\Controller;

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

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

Common Mistakes

  1. Not using autowiring: If a service constructor changes, autowiring automatically updates the container. Manually wiring services in services.yaml is error-prone.
  2. Putting business logic in controllers: Controllers should only handle HTTP concerns. Move logic to services or entities for testability.
  3. Forgetting to run migrations after entity changes: Doctrine checks the schema against the database. If you add a field but do not migrate, you get a database error.
  4. Using $this->get() instead of constructor injection: The old Controller::get() method is deprecated. Use constructor injection or action-level dependency injection for services.
  5. Not clearing the cache after configuration changes: Symfony caches configuration. Run php bin/console cache:clear if your changes do not take effect.

Practice Questions

  1. What is the purpose of autowiring in Symfony?

    • Autowiring automatically resolves constructor dependencies from the service container, eliminating the need for manual service configuration in most cases.
  2. How do you define a route with a numeric parameter in Symfony?

    • Use the requirements parameter in the Route attribute: #[Route('/product/{id}', requirements: ['id' => '\d+'])].
  3. What is Twig's extends tag used for?

    • It tells Twig that the current template inherits from a parent template, filling in blocks defined by the parent.
  4. How do you create a database table in Symfony?

    • Create an entity with php bin/console make:entity, generate a Migration with make:migration, and run it with doctrine:migrations:migrate.
  5. Challenge: Build a contact management system with Symfony that has CRUD operations, form validation, and a Twig-based UI. Use Doctrine relationships to link contacts to categories.

Mini Project

Create a job board application with Symfony:

  • Define a Job entity with fields: title, company, description, location, salary, and created_at.
  • Create a form type for job submissions with validation rules.
  • Build routes and controllers for listing jobs, viewing a single job, and posting a new job.
  • Use Twig templates with a base layout and form theming.
  • Add a search feature that filters jobs by title or location using a Doctrine query.
  • Protect the job posting route with Symfony's security component (form login).

FAQ

What is Symfony?

Symfony is a PHP framework and set of reusable components for building web applications, APIs, and microservices with a focus on performance and extensibility.

How do I install Symfony?

Use Composer: composer create-project symfony/skeleton my-app. Run symfony server:start or php -S 127.0.0.1:8000 -t public/ to start.

What is Doctrine?

Doctrine is the default ORM for Symfony. It maps PHP classes (entities) to database tables and provides a query builder, DQL, and migration system.

What are Symfony bundles?

Bundles are packages that group related functionality. Symfony itself is a collection of bundles. Your application code can also be organized as bundles.

Does Symfony work with PHP 8?

Yes. Symfony 7 requires PHP 8.2 and uses PHP 8 features extensively, including attributes for routing, constructor promotion, and named arguments.

What's Next

Now that you know both Laravel and Symfony, learn how to secure your PHP applications in the Security lesson.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro