article

Symfony 7.4 LTS - The Last Stop Before Symfony 8

20th October 2025

The LTS That Matters

Every two years, Symfony ships a Long-Term Support release. These are the versions you actually want to run in production.

Symfony 7.4 is that release - dropping late November 2025. You get:

  • Bug fixes until November 2028 (3 years)
  • Security fixes until November 2029 (4 years)

But here's what makes this release interesting: Symfony 8.0 launches at the same time with identical features, but 8.0 removes all deprecated code and bumps minimum requirements.

Think of 7.4 as your bridge. Upgrade to 7.4, fix all deprecation warnings, then jumping to 8.0 is trivial.

XML Configuration: It's Over

The big one: XML configuration is officially deprecated in 7.4 and completely gone in 8.0.

If you're still writing this:

<!-- services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services">
    <services>
        <service id="App\Service\UserService" autowire="true" />
    </services>
</container>

Time to migrate. Your options: YAML (default) or PHP configuration.

YAML with JSON Schemas

YAML now has IDE validation:

# config/services.yaml
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json

parameters:
  app.api_key: '%env(API_KEY)%'

services:
  _defaults:
    autowire: true
    autoconfigure: true

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

Your IDE will now autocomplete and validate configuration. No more typos breaking your builds.

PHP Configuration (Type-Safe)

Or go full type-safe:

// config/packages/framework.php
namespace Symfony\Config;

return static function (FrameworkConfig $framework) {
    $framework
        ->secret(env('APP_SECRET'))
        ->httpCache()
            ->enabled(true);

    $framework
        ->session()
            ->handlerId(null)
            ->cookieSecure('auto')
            ->cookieSamesite('lax');
};

Autocomplete everywhere. Type checking at the language level. No runtime surprises from config typos.

For reusable bundles still on XML, Symfony provides a migration tool to convert XML → PHP automatically.

UUID v7: Better Database Performance

Symfony's UUID component now defaults to UUIDv7 instead of v4.

use Symfony\Component\Uid\Factory\UuidFactory;

class ProductService {
    public function __construct(
        private UuidFactory $uuidFactory,
    ) {}

    public function createProduct(): void {
        // Generates UUIDv7 by default (time-ordered)
        $uuid = $this->uuidFactory->create();

        // Or be explicit
        $uuid = $this->uuidFactory->timeBased()->create();
    }
}

Why this matters:

UUIDv7 includes a timestamp (Unix Epoch + microseconds) in the UUID itself. This means:

  • Better database indexing (values are naturally ordered)
  • Less index fragmentation in high-volume tables
  • 10% faster generation compared to v4

For a typical CRUD app? You won't notice. For apps with millions of records? This adds up.

MockUuidFactory for Testing

Testing code that generates UUIDs used to be painful. Not anymore:

use Symfony\Component\Uid\Factory\MockUuidFactory;
use Symfony\Component\Uid\UuidV4;

class UserServiceTest extends TestCase {
    public function testCreateUser(): void {
        $factory = new MockUuidFactory([
            UuidV4::fromString('11111111-1111-4111-8111-111111111111'),
            UuidV4::fromString('22222222-2222-4222-8222-222222222222'),
        ]);

        $service = new UserService($factory);

        $userId1 = $service->createUserId();
        $userId2 = $service->createUserId();

        $this->assertSame('11111111-1111-4111-8111-111111111111', $userId1);
        $this->assertSame('22222222-2222-4222-8222-222222222222', $userId2);
    }
}

Deterministic UUIDs in tests. Finally.

Security Voter Improvements

This is a nice DX upgrade. You can now get detailed feedback about authorization decisions:

{% set decision = access_decision('post_edit', post) %}

{% if decision.isGranted() %}
    <a href="{{ path('post_edit', {id: post.id}) }}">Edit</a>
{% else %}
    <p class="error">{{ decision.message }}</p>
    {# Shows actual reason: "You must be the author" #}
{% endif %}

Instead of just true/false, you get context about why access was denied.

In your voter:

use Symfony\Component\Security\Core\Authorization\Voter\Vote;

class BlogPostVoter extends Voter {
    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token,
        ?Vote $vote = null
    ): bool {
        $user = $token->getUser();

        if (!$user instanceof User) {
            $vote?->extraData->set('reason', 'Must be logged in');
            return false;
        }

        if ($subject->getAuthor() !== $user) {
            $vote?->extraData->set('reason', 'You must be the author');
            return false;
        }

        return true;
    }
}

Better error messages for users, less "why can't I do this?" support tickets.

HTTP Client Caching: RFC 9111 Compliant

Symfony's HTTP client gets real caching:

# config/packages/framework.yaml
framework:
  cache:
    pools:
      api_cache_pool:
        adapter: cache.adapter.redis_tag_aware
        tags: true

  http_client:
    scoped_clients:
      api.client:
        base_uri: 'https://api.example.com'
        caching:
          cache_pool: api_cache_pool

The client now respects HTTP cache headers (Cache-Control, ETag, etc.) automatically. No more manual cache logic for API clients.

Plus tag-aware caching means you can invalidate related cached responses together:

$cache->invalidateTags(['user_123']);

All cached API responses tagged with that user are instantly gone.

ControllerHelper: Helpers Without Inheritance

I never liked extending AbstractController just to get helper methods. Symfony 7.4 fixes this:

use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper;

class MyInvokableController {
    public function __construct(
        private ControllerHelper $helper,
    ) {}

    public function __invoke(): Response {
        // All the helper methods, no inheritance
        return $this->helper->render('template.html.twig', [
            'data' => $someData,
        ]);
    }
}

Composition over inheritance. Modern PHP practices.

What Gets Deprecated

These work in 7.4 with deprecation warnings, gone in 8.0:

  1. XML configuration for everything (services, routes, validation, etc.)
  2. Fluent PHP format for semantic config
  3. Session config options following PHP 8.4's deprecations
  4. !tagged YAML tag → use !tagged_iterator instead
  5. Empty user identifiers in UserBadge constructor
  6. TranslatableMessage::__toString() → use trans() method

If you're on Symfony 6.4 or 7.x already, you've probably seen warnings about most of these.

Upgrade Path: The Smart Way

Don't jump from 6.4 to 7.4 in one go.

Here's the plan:

  1. Upgrade to Symfony 6.4 LTS (if not already)
  2. Run bin/console debug:container --deprecations
  3. Fix ALL deprecation warnings
  4. Upgrade to 7.4 LTS (smooth sailing)
  5. Fix new 7.4 deprecations
  6. Upgrade to 8.0 when ready (trivial at this point)

Symfony deprecations are well-documented. They tell you exactly what to change. Don't ignore them.

Performance Notes

Symfony 7.4 doesn't have massive performance improvements over 7.3. The big wins came earlier:

  • Symfony 6.2: Native attributes instead of annotations
  • Symfony 6.3: Various profiler and cache improvements
  • Symfony 7.0: PHP 8.2+ features

But the UUID v7 default helps high-volume apps. The HTTP client caching reduces external API calls. Small improvements that add up.

What I'm Actually Using This For

I've got three apps on Symfony 6.4 LTS right now. All three are getting upgraded to 7.4 in December:

Why?

  • 4 years of security updates vs 6.4's remaining 2 years
  • Better DX with the new UUID factory
  • HTTP client caching will reduce our API costs
  • Cleaner controller code with ControllerHelper

Why not wait for 8.0?

  • 7.4 gives me time to fix deprecations gradually
  • Production apps don't need to be on the bleeding edge
  • LTS releases are safer for production

When to Upgrade

New projects: Use Symfony 7.4 LTS when it drops (late November).

Existing apps on 6.4 LTS: Upgrade within the next 3-6 months. You have until November 2026 for security updates, but why wait?

Existing apps on 7.x: Upgrade to 7.4 as soon as it's stable. Small changes, long-term support.

Existing apps on 5.x or older: Upgrade to 6.4 LTS first. Jumping multiple major versions is asking for pain.

The Bottom Line

Symfony 7.4 is a stabilization release with long-term support. Not flashy, but solid.

The XML deprecation will affect some teams. The UUID v7 default is a smart move. The HTTP client improvements are genuinely useful.

But the real value? 4 years of security updates. That's what LTS is about.

If you run Symfony in production, 7.4 is the version you want. Mark your calendar for late November.

Resources

Plan ahead. LTS releases matter. 🚀

Ready to Transform Your Business?

Let us work together to bring your ideas to life with cutting-edge technology and innovative solutions.

Get Started Today

© Copyright 2025 EvolutIT