Last updated on 24th February 2026

Symfony: The Complete Guide to Building and Deploying PHP Applications

Symfony is a high-performance PHP framework and a collection of decoupled, reusable PHP components that power some of the most demanding applications on the web. Released in 2005 by Fabien Potencier and maintained by SensioLabs, Symfony has become the backbone of the PHP ecosystem — its components are used by Laravel, Drupal, Magento, and countless other frameworks and platforms. Whether you are building a REST API, a traditional server-rendered web application, or a complex enterprise platform, Symfony provides the structure, tooling, and conventions to do it properly.

Why Symfony Matters for Web Developers

Symfony occupies a unique position in the PHP landscape. Where other frameworks aim for simplicity and quick starts, Symfony is designed for long-term maintainability and enterprise-grade reliability. This does not mean it is slow to get started with — the Symfony CLI and Flex recipe system make project bootstrapping fast — but it does mean the framework makes deliberate choices about architecture that pay dividends as applications grow in complexity.

One of Symfony's most significant differentiators is its LTS (Long-Term Support) release cadence. Major versions receive bug fixes for three years and security patches for five. For development teams building applications that must be maintained over years, not months, this stability guarantee is invaluable. Symfony 7.x, the current major version, requires PHP 8.2 or higher and brings full support for PHP's modern features including fibers, enums, readonly properties, and union types.

The framework is also a component ecosystem, not just an application skeleton. The Symfony Console component powers Laravel's Artisan and Drupal's Drush CLI tools. The HttpFoundation component defines how PHP handles HTTP requests and responses across much of the ecosystem. This composability means that learning Symfony is learning the shared vocabulary of modern PHP development.

Finally, Symfony's developer experience tooling has matured considerably. The web profiler toolbar gives you detailed insight into every request — queries executed, events fired, cache hits and misses, memory usage, and more. Combined with the Flex system that automatically configures packages as you install them, Symfony delivers a workflow that stays productive as your project scales.

Step 1: System Requirements

PHP version:

Symfony 7.x requires PHP 8.2 or higher. PHP 8.3 is recommended.

php --version

Required PHP extensions:

  • ctype, iconv, json, mbstring, openssl, pcre, session, simplexml, tokenizer

Composer:

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

Symfony CLI (recommended):

curl -sS https://get.symfony.com/cli/installer | bash

Step 2: Install Symfony

Using the Symfony CLI:

# Full web application
symfony new my_project --version="7.2.*" --webapp

# Minimal project (API or microservice)
symfony new my_project --version="7.2.*"

Using Composer:

composer create-project symfony/skeleton:"7.2.*" my_project
cd my_project
composer require webapp

Start the development server:

symfony serve

Step 3: Project Structure

my_project/
├── bin/
│   └── console              # Symfony console entry point
├── config/
│   ├── packages/            # Bundle configuration files
│   ├── routes/              # Route definitions
│   └── services.yaml        # Service container configuration
├── migrations/              # Doctrine database migrations
├── public/
│   └── index.php            # Front controller (document root)
├── src/
│   ├── Controller/          # HTTP controllers
│   ├── Entity/              # Doctrine entities
│   ├── Form/                # Form type classes
│   ├── Repository/          # Doctrine repositories
│   └── Kernel.php           # Application kernel
├── templates/               # Twig templates
├── tests/                   # PHPUnit test suites
├── var/
│   ├── cache/               # Compiled container, routes, Twig
│   └── log/                 # Application logs
├── vendor/                  # Composer dependencies
├── .env                     # Default environment variables
└── composer.json

Key: public/ is the only directory exposed to the web. Your web server's document root must point here.

Step 4: Configure Symfony for Your Workflow

Environment configuration:

# .env
APP_ENV=dev
APP_SECRET=your-32-character-secret-here
DATABASE_URL="postgresql://app:password@127.0.0.1:5432/app"

Service container:

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

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

Step 5: Core Features

Routing and Controllers

<?php
namespace App\Controller;

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

#[Route('/posts')]
class PostController extends AbstractController
{
    #[Route('/', name: 'post_index', methods: ['GET'])]
    public function index(): Response
    {
        return $this->render('post/index.html.twig', [
            'posts' => [],
        ]);
    }

    #[Route('/{id}', name: 'post_show', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function show(int $id): Response
    {
        return $this->render('post/show.html.twig', ['id' => $id]);
    }
}

Twig Templates

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

{% block title %}Blog Posts{% endblock %}

{% block body %}
    <h1>All Posts</h1>
    {% for post in posts %}
        <article>
            <h2><a href="{{ path('post_show', { id: post.id }) }}">{{ post.title }}</a></h2>
        </article>
    {% endfor %}
{% endblock %}

Doctrine ORM

<?php
namespace App\Entity;

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

#[ORM\Entity(repositoryClass: PostRepository::class)]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: 'text')]
    private ?string $content = null;
}
php bin/console make:migration
php bin/console doctrine:migrations:migrate

Building with Symfony? DeployHQ connects your Git repo to any server and deploys automatically when you push — SFTP, SSH, or cloud. Try it free.


Step 6: Advanced Features

Event System

<?php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class PostPublishedSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [PostPublishedEvent::class => 'onPostPublished'];
    }

    public function onPostPublished(PostPublishedEvent $event): void
    {
        $post = $event->getPost();
    }
}

Symfony Messenger (Async Processing)

$this->messageBus->dispatch(new SendWelcomeEmailMessage($user->getId()));
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            App\Message\SendWelcomeEmailMessage: async

API Platform

composer require api-platform/core
<?php
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
#[ORM\Entity]
class Post
{
    // Entity definition...
}

Caching

public function getPopularPosts(): array
{
    return $this->cache->get('popular_posts', function (ItemInterface $item): array {
        $item->expiresAfter(3600);
        return $this->repository->findMostViewed(10);
    });
}

Step 7: Best Practices

Testing with PHPUnit

<?php
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PostControllerTest extends WebTestCase
{
    public function testPostIndexReturns200(): void
    {
        $client = static::createClient();
        $client->request('GET', '/posts');
        $this->assertResponseIsSuccessful();
    }
}

Performance Optimisation

Opcache in php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.validate_timestamps=0

Composer autoloader:

composer install --no-dev --optimize-autoloader

Cache warmup:

php bin/console cache:warmup --env=prod

Step 8: Deploy with DeployHQ

Build Commands

composer install --no-dev --optimize-autoloader --no-interaction
php bin/console cache:clear --env=prod --no-debug
php bin/console cache:warmup --env=prod --no-debug
php bin/console doctrine:migrations:migrate --env=prod --no-interaction

Nginx Configuration

server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/my_project/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}

Environment Variables on the Server

# /var/www/my_project/.env.local
APP_ENV=prod
APP_SECRET=your-production-secret-here
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
MAILER_DSN=smtp://user:pass@smtp.example.com

Messenger Workers

; /etc/supervisor/conf.d/symfony-messenger.conf
[program:symfony-messenger]
command=php /var/www/my_project/bin/console messenger:consume async --limit=50 --memory-limit=128M
directory=/var/www/my_project
autostart=true
autorestart=true
user=www-data

File Permissions

sudo chown -R www-data:www-data /var/www/my_project/var
sudo chmod -R 775 /var/www/my_project/var

Step 9: Troubleshooting

500 Internal Server Error with no output:

tail -f var/log/prod.log

"No such file or directory" for var/cache/prod/:

rm -rf var/cache/prod
php bin/console cache:warmup --env=prod

Composer install fails on the server:

Specify your target platform in composer.json:

{
    "config": {
        "platform": {
            "php": "8.3"
        }
    }
}

Class not found after deployment:

composer dump-autoload --optimize --no-dev

Environment variables not loading:

php bin/console debug:container --env-vars

Conclusion

Symfony is one of the most capable PHP frameworks available, and its influence extends far beyond projects that use it directly. Its enterprise-grade reliability, long-term support commitments, and powerful tooling make it a strong choice for applications that need to scale and be maintained over years.

Getting Symfony right in production requires attention to the details: production-mode compilation, optimised autoloading, cache warmup, proper migration handling, and correct web server configuration with public/ as the document root. DeployHQ handles this sequence automatically — connecting your Git repository to your server and running your build commands, migrations, and cache operations every time you push.


If you have questions about deploying your Symfony projects, reach out to us at support@deployhq.com or find us on Twitter/X.