Symfony Framework Introduction: Build Robust PHP Apps
Learn Symfony framework from scratch: routing, controllers, Doctrine ORM, Twig templates, forms, security, and testing.
20+ years shipping production PHP systems at scale. Lessons pulled from things that broke in production.
- ✓Basic knowledge of PHP (variables, functions, OOP)
- ✓Familiarity with MVC pattern
- ✓Composer installed
- ✓PHP 8.1 or higher
- Symfony is a PHP framework for building web applications and APIs.
- It follows the MVC pattern and uses reusable components.
- Key features: routing, Doctrine ORM, Twig templating, form handling, security.
- Symfony promotes best practices like dependency injection and testing.
- Ideal for large-scale, maintainable projects.
Think of Symfony as a pre-built toolkit for building a house. Instead of making each brick and nail from scratch, Symfony gives you ready-made walls, doors, and windows (components) that you can assemble quickly and safely. It also provides a blueprint (MVC pattern) to keep everything organized.
Building a web application from scratch can be overwhelming. You need to handle routing, database interactions, form validation, security, and more. Symfony, one of the most popular PHP frameworks, provides a structured and reusable set of components that streamline development. It follows the Model-View-Controller (MVC) pattern, separating business logic from presentation. Symfony is used by thousands of projects, from small websites to enterprise applications like Drupal and phpBB. In this tutorial, you'll learn the core concepts of Symfony: installation, routing, controllers, Doctrine ORM, Twig templates, forms, security, and testing. By the end, you'll be able to build a fully functional web application with best practices. Let's dive in!
Installing Symfony and Setting Up a Project
Symfony provides a flexible installer. First, ensure you have PHP 8.1+ and Composer installed. Then create a new project using the Symfony CLI or Composer. The Symfony CLI offers a built-in web server and tools. Alternatively, use Composer: composer create-project symfony/skeleton my_project. This creates a minimal skeleton. For a full-featured web application, use symfony/website-skeleton. After installation, start the server with symfony server:start or use PHP's built-in server. Verify by visiting http://localhost:8000. You'll see the Symfony welcome page. The project structure includes config/, src/, templates/, and var/ directories. The config/ folder holds YAML files for routing, services, and Doctrine. The src/ folder contains your PHP code (controllers, entities, etc.). The templates/ folder stores Twig templates. The var/ folder is for cache and logs. Symfony uses environment variables for configuration; check the .env file for database settings.
.env files to version control.Understanding Routing and Controllers
Routing maps URLs to controller methods. In Symfony, routes can be defined via annotations, YAML, XML, or PHP attributes. The most common is using annotations (or PHP 8 attributes) directly in the controller. A controller is a PHP class that extends AbstractController. Each public method can be a route. For example, a route for the homepage: #[Route('/', name: 'home')]. The method returns a Response object. Symfony's AbstractController provides shortcuts like render() for Twig templates and json() for APIs. You can also pass parameters in the URL, like #[Route('/blog/{slug}', name: 'blog_show')]. The {slug} is automatically injected into the method. Controllers can access request data via the Request object. Symfony's routing is flexible and supports requirements, defaults, and HTTP methods. Use methods: ['GET'] to restrict. You can also generate URLs from route names using $this->generateUrl(). This decouples views from hardcoded paths.
app.php or app_dev.php in URLs. Symfony uses a front controller (index.php) and routing handles everything.Working with Doctrine ORM and Database
Doctrine is Symfony's default ORM. It maps PHP objects to database tables. First, configure the database connection in .env (e.g., DATABASE_URL="mysql://user:pass@127.0.0.1:3306/db_name"). Then create an entity: a PHP class that represents a table. Use annotations or attributes to define mappings. For example, a Product entity with id, name, and price. Run php bin/console make:entity to generate the class. Then create the database schema: php bin/console doctrine:database:create and php bin/console doctrine:schema:update --force. To interact with the database, use the EntityManager. In a controller, inject EntityManagerInterface and use $entityManager->persist($product) and $entityManager->flush(). Doctrine supports relationships: OneToMany, ManyToOne, ManyToMany. For example, a Category can have many Products. Use the #[OneToMany] attribute. Doctrine also provides a query builder and DQL for complex queries. Repositories are classes that encapsulate query logic. Generate them with make:entity or manually. For production, use migrations instead of schema:update to track changes.
php bin/console doctrine:schema:validate. This catches mapping errors early.Templating with Twig
Twig is Symfony's templating engine. It's fast, secure, and extensible. Templates are stored in templates/. Use Twig syntax: {{ variable }} for output, {% if %} for logic, and {# comment #} for comments. Twig auto-escapes output to prevent XSS. You can extend layouts using {% extends 'base.html.twig' %} and define blocks with {% block content %}. Twig includes functions like path() to generate URLs from route names, and asset() for static files. Filters modify output: {{ name|upper }}, {{ date|date('Y-m-d') }}. You can also create custom filters and functions. Twig supports macros for reusable HTML snippets. In controllers, use $this->render('template.html.twig', ['key' => 'value']) to pass variables. Twig also handles forms rendering with {{ form(form) }} or individual fields. For performance, enable Twig cache in production.
strict_variables option in production to catch undefined variables early. Set it in config/packages/twig.yaml.Handling Forms and Validation
Symfony's Form component simplifies form creation and validation. Create a form class that extends AbstractType. Define fields using $builder->add('field', TextType::class). Use options like required, label, and constraints. For validation, add constraints via annotations or attributes in the entity. For example, #[Assert\NotBlank] on a property. In the controller, create the form with $form = $this->createForm(ProductType::class, $product). Handle the request: $form->handleRequest($request). If the form is submitted and valid, persist the entity. Symfony automatically validates constraints. You can also create custom constraints. Forms can be rendered in Twig with {{ form_start(form) }}, {{ form_widget(form) }}, and {{ form_end(form) }}. For AJAX submissions, return JSON responses. Symfony also provides CSRF protection by default. Ensure your forms include the CSRF token field.
Security: Authentication and Authorization
Symfony Security provides authentication (who you are) and authorization (what you can do). Configure security in config/packages/security.yaml. Define firewalls for different parts of your app. For example, a main firewall for the site. Use form login, HTTP basic, or API tokens. For user management, create a User entity implementing UserInterface. Use make:user to generate it. Store passwords hashed with UserPasswordEncoderInterface. In controllers, use $this->denyAccessUnlessGranted('ROLE_ADMIN') to check roles. Twig has function. Symfony also supports voters for custom authorization logic. For API authentication, use JWT or API tokens. Symfony's security component is highly configurable. Always use HTTPS in production. Remember to logout: is_granted()$this->redirectToRoute('app_logout').
security:false for the dev firewall to avoid login prompts during development. But ensure it's not exposed in production.Testing with PHPUnit
Symfony integrates with PHPUnit for testing. Tests are stored in tests/. Use make:test to generate test classes. For unit tests, test individual classes. For functional tests, simulate HTTP requests using Symfony's WebTestCase. Create a client and make requests: $client->request('GET', '/'). Assert status codes, content, and selectors. Use assertSelectorTextContains() to check HTML. For database testing, use a separate test database and load fixtures. Symfony's Panther extends PHPUnit for browser testing with JavaScript. Always run tests before deployment: php bin/phpunit. Use data providers for multiple scenarios. Mock external services with MockObject. Testing ensures code quality and prevents regressions.
The Case of the Missing CSRF Token
- Always include CSRF tokens in forms, especially when using AJAX.
- Test form submissions with JavaScript disabled to catch token issues.
- Use Symfony's built-in form theming to ensure tokens are rendered.
- Monitor error logs for 400 errors to detect CSRF failures early.
- Educate frontend developers about Symfony's security requirements.
php bin/console debug:router to see registered routes. Verify route name and parameters.flush() is called after persist(). Verify transaction boundaries and error logs.php bin/console debug:container --parameters | grep csrfCheck form theme templates| File | Command / Code | Purpose |
|---|---|---|
| install.sh | composer create-project symfony/website-skeleton my_project | Installing Symfony and Setting Up a Project |
| src | namespace App\Controller; | Understanding Routing and Controllers |
| src | namespace App\Entity; | Working with Doctrine ORM and Database |
| templates | {% extends 'base.html.twig' %} | Templating with Twig |
| src | namespace App\Form; | Handling Forms and Validation |
| config | security: | Security |
| tests | namespace App\Tests\Controller; | Testing with PHPUnit |
Key takeaways
Common mistakes to avoid
5 patternsForgetting to call `flush()` after `persist()`
Using `schema:update` in production
Not escaping output in Twig
Hardcoding URLs instead of using `path()`
Ignoring environment variables for configuration
Interview Questions on This Topic
What is dependency injection in Symfony?
services.yaml and can be autowired.Frequently Asked Questions
20+ years shipping production PHP systems at scale. Lessons pulled from things that broke in production.
That's Advanced PHP. Mark it forged?
3 min read · try the examples if you haven't