Skip to content

Symfony

Rumen Damyanov edited this page Jul 29, 2025 · 1 revision

Symfony Examples

This guide shows how to use php-feed with Symfony applications.

Installation

Add the package to your Symfony project:

composer require rumenx/php-feed

No bundle registration is needed - the package works out of the box!

Basic Symfony Controller

Create a controller to handle RSS/Atom feeds:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Rumenx\Feed\FeedFactory;
use App\Repository\PostRepository;

class FeedController extends AbstractController
{
    #[Route('/feed', name: 'feed_posts', methods: ['GET'])]
    public function posts(PostRepository $postRepository): Response
    {
        $feed = FeedFactory::create();
        $feed->setTitle($this->getParameter('app.name') . ' - Latest Posts');
        $feed->setDescription('Latest blog posts');
        $feed->setLink($this->generateUrl('homepage', [], urlType: 'absolute'));
        $feed->setLanguage('en');

        // Get latest published posts
        $posts = $postRepository->findLatestPublished(20);

        foreach ($posts as $post) {
            $feed->addItem([
                'title' => $post->getTitle(),
                'author' => $post->getAuthor()->getName(),
                'link' => $this->generateUrl('post_show', ['slug' => $post->getSlug()], urlType: 'absolute'),
                'pubdate' => $post->getCreatedAt()->format('c'),
                'description' => $post->getExcerpt(),
                'category' => array_map(fn($cat) => $cat->getName(), $post->getCategories()->toArray())
            ]);
        }

        $xml = $feed->render('rss');

        return new Response($xml, 200, [
            'Content-Type' => 'application/rss+xml; charset=utf-8'
        ]);
    }
}

Using Doctrine Entities

Here's how to create feeds from Doctrine entities:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Rumenx\Feed\FeedFactory;
use App\Repository\PostRepository;
use App\Repository\NewsRepository;
use App\Repository\ProductRepository;

class FeedController extends AbstractController
{
    #[Route('/feed/posts', name: 'feed_posts')]
    public function posts(PostRepository $repository): Response
    {
        return $this->createFeed(
            $repository->findLatestPublished(20),
            'Blog Posts',
            'Latest blog posts',
            'post_show'
        );
    }

    #[Route('/feed/news', name: 'feed_news')]
    public function news(NewsRepository $repository): Response
    {
        return $this->createFeed(
            $repository->findLatestPublished(20),
            'News Updates',
            'Latest news and updates',
            'news_show'
        );
    }

    #[Route('/feed/products', name: 'feed_products')]
    public function products(ProductRepository $repository): Response
    {
        return $this->createFeed(
            $repository->findLatestAvailable(50),
            'New Products',
            'Latest products in our store',
            'product_show'
        );
    }

    private function createFeed(array $items, string $title, string $description, string $route): Response
    {
        $feed = FeedFactory::create();
        $feed->setTitle($this->getParameter('app.name') . ' - ' . $title);
        $feed->setDescription($description);
        $feed->setLink($this->generateUrl('homepage', [], urlType: 'absolute'));
        $feed->setLanguage($this->getParameter('locale'));

        foreach ($items as $item) {
            $feed->addItem([
                'title' => $item->getTitle(),
                'author' => $item->getAuthor()?->getName() ?? 'Unknown',
                'link' => $this->generateUrl($route, ['slug' => $item->getSlug()], urlType: 'absolute'),
                'pubdate' => $item->getCreatedAt()->format('c'),
                'description' => $item->getExcerpt() ?? $item->getDescription() ?? substr($item->getContent(), 0, 300),
                'category' => array_map(fn($cat) => $cat->getName(), $item->getCategories()->toArray())
            ]);
        }

        return new Response($feed->render('rss'), 200, [
            'Content-Type' => 'application/rss+xml; charset=utf-8'
        ]);
    }
}

Repository Methods

Example repository methods to fetch feed content:

<?php

namespace App\Repository;

use App\Entity\Post;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class PostRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Post::class);
    }

    /**
     * @return Post[]
     */
    public function findLatestPublished(int $limit = 20): array
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.published = :published')
            ->setParameter('published', true)
            ->orderBy('p.createdAt', 'DESC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }

    /**
     * @return Post[]
     */
    public function findByCategory(string $categorySlug, int $limit = 20): array
    {
        return $this->createQueryBuilder('p')
            ->join('p.categories', 'c')
            ->andWhere('c.slug = :categorySlug')
            ->andWhere('p.published = :published')
            ->setParameter('categorySlug', $categorySlug)
            ->setParameter('published', true)
            ->orderBy('p.createdAt', 'DESC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }
}

Routes Configuration

Configure your feed routes in config/routes.yaml:

# config/routes.yaml
feed_posts:
    path: /feed
    controller: App\Controller\FeedController::posts
    methods: [GET]

feed_rss:
    path: /feed.rss
    controller: App\Controller\FeedController::posts
    methods: [GET]

feed_xml:
    path: /feed.xml
    controller: App\Controller\FeedController::posts
    methods: [GET]

feed_atom:
    path: /feed.atom
    controller: App\Controller\FeedController::atom
    methods: [GET]

# Category-specific feeds
feed_category:
    path: /feed/category/{slug}
    controller: App\Controller\FeedController::category
    methods: [GET]
    requirements:
        slug: '[a-z0-9-]+'

Feed Discovery in Twig Templates

Add feed discovery links to your base template:

In templates/base.html.twig:

<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{{ app_name }}{% endblock %}</title>
    
    {# Feed Discovery Links #}
    <link rel="alternate" type="application/rss+xml" title="{{ app_name }} - RSS Feed" href="{{ url('feed_rss') }}">
    <link rel="alternate" type="application/atom+xml" title="{{ app_name }} - Atom Feed" href="{{ url('feed_atom') }}">
    
    {% block stylesheets %}{% endblock %}
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>

Service for Feed Generation

Create a dedicated service for feed generation:

<?php

namespace App\Service;

use Rumenx\Feed\FeedFactory;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class FeedService
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
        private string $appName,
        private string $appLocale = 'en'
    ) {}

    public function createFeed(string $title, string $description, string $link = ''): object
    {
        $feed = FeedFactory::create();
        $feed->setTitle($this->appName . ' - ' . $title);
        $feed->setDescription($description);
        $feed->setLink($link ?: $this->urlGenerator->generate('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL));
        $feed->setLanguage($this->appLocale);

        return $feed;
    }

    public function addEntityItem(object $feed, object $entity, string $route, array $routeParams = []): void
    {
        $feed->addItem([
            'title' => $entity->getTitle(),
            'author' => $entity->getAuthor()?->getName() ?? 'Unknown',
            'link' => $this->urlGenerator->generate($route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL),
            'pubdate' => $entity->getCreatedAt()->format('c'),
            'description' => $entity->getExcerpt() ?? $entity->getDescription() ?? '',
            'category' => $this->extractCategories($entity)
        ]);
    }

    private function extractCategories(object $entity): array
    {
        if (method_exists($entity, 'getCategories')) {
            return array_map(fn($cat) => $cat->getName(), $entity->getCategories()->toArray());
        }

        return [];
    }
}

Register the service in config/services.yaml:

# config/services.yaml
services:
    App\Service\FeedService:
        arguments:
            $appName: '%app.name%'
            $appLocale: '%locale%'

Use the service in your controller:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Service\FeedService;
use App\Repository\PostRepository;

class FeedController extends AbstractController
{
    #[Route('/feed', name: 'feed_posts')]
    public function posts(FeedService $feedService, PostRepository $postRepository): Response
    {
        $feed = $feedService->createFeed('Latest Posts', 'Latest blog posts from our website');
        
        $posts = $postRepository->findLatestPublished(20);

        foreach ($posts as $post) {
            $feedService->addEntityItem($feed, $post, 'post_show', ['slug' => $post->getSlug()]);
        }

        return new Response($feed->render('rss'), 200, [
            'Content-Type' => 'application/rss+xml; charset=utf-8'
        ]);
    }
}

Caching with Symfony Cache

Example with Symfony's cache component:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Rumenx\Feed\FeedFactory;
use App\Repository\PostRepository;

class FeedController extends AbstractController
{
    #[Route('/feed', name: 'feed_posts')]
    public function posts(CacheInterface $cache, PostRepository $postRepository): Response
    {
        $feedXml = $cache->get('blog_feed_rss', function (ItemInterface $item) use ($postRepository) {
            $item->expiresAfter(3600); // Cache for 1 hour

            return $this->generatePostsFeed($postRepository);
        });

        return new Response($feedXml, 200, [
            'Content-Type' => 'application/rss+xml; charset=utf-8',
            'Cache-Control' => 'public, max-age=3600'
        ]);
    }

    private function generatePostsFeed(PostRepository $postRepository): string
    {
        $feed = FeedFactory::create();
        $feed->setTitle($this->getParameter('app.name') . ' - Blog Posts');
        $feed->setDescription('Latest posts from our blog');
        $feed->setLink($this->generateUrl('homepage', [], urlType: 'absolute'));

        $posts = $postRepository->findLatestPublished(20);

        foreach ($posts as $post) {
            $feed->addItem([
                'title' => $post->getTitle(),
                'author' => $post->getAuthor()->getName(),
                'link' => $this->generateUrl('post_show', ['slug' => $post->getSlug()], urlType: 'absolute'),
                'pubdate' => $post->getCreatedAt()->format('c'),
                'description' => $post->getExcerpt()
            ]);
        }

        return $feed->render('rss');
    }
}

Podcast Feed with Media Enclosures

Create a podcast feed with audio files:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Rumenx\Feed\FeedFactory;
use App\Repository\EpisodeRepository;

class PodcastController extends AbstractController
{
    #[Route('/podcast/feed', name: 'podcast_feed')]
    public function feed(EpisodeRepository $episodeRepository): Response
    {
        $feed = FeedFactory::create();
        $feed->setTitle('My Podcast');
        $feed->setDescription('Weekly episodes about technology and programming');
        $feed->setLink($this->generateUrl('podcast_index', [], urlType: 'absolute'));
        $feed->setLanguage('en');

        $episodes = $episodeRepository->findPublished(50);

        foreach ($episodes as $episode) {
            $feed->addItem([
                'title' => $episode->getTitle(),
                'author' => 'Podcast Host',
                'link' => $this->generateUrl('podcast_episode', ['slug' => $episode->getSlug()], urlType: 'absolute'),
                'pubdate' => $episode->getPublishedAt()->format('c'),
                'description' => $episode->getDescription(),
                'enclosure' => [
                    'url' => $episode->getAudioUrl(),
                    'type' => 'audio/mpeg',
                    'length' => $episode->getFileSize()
                ]
            ]);
        }

        return new Response($feed->render('rss'), 200, [
            'Content-Type' => 'application/rss+xml; charset=utf-8'
        ]);
    }
}

Console Command for Static Feed Generation

Create a console command to generate static feed files:

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
use Rumenx\Feed\FeedFactory;
use App\Repository\PostRepository;

#[AsCommand(
    name: 'feeds:generate',
    description: 'Generate static RSS and Atom feed files'
)]
class GenerateFeedsCommand extends Command
{
    public function __construct(
        private PostRepository $postRepository,
        private string $publicDir
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $filesystem = new Filesystem();

        $io->title('Generating RSS feeds');

        // Generate RSS feed
        $rssFeed = $this->createFeed('rss');
        $filesystem->dumpFile($this->publicDir . '/feed.rss', $rssFeed);
        
        // Generate Atom feed
        $atomFeed = $this->createFeed('atom');
        $filesystem->dumpFile($this->publicDir . '/feed.atom', $atomFeed);

        $io->success('Feeds generated successfully!');

        return Command::SUCCESS;
    }

    private function createFeed(string $format): string
    {
        $feed = FeedFactory::create();
        $feed->setTitle('My Blog');
        $feed->setDescription('Latest posts from my blog');
        $feed->setLink('https://example.com');

        $posts = $this->postRepository->findLatestPublished(20);

        foreach ($posts as $post) {
            $feed->addItem([
                'title' => $post->getTitle(),
                'author' => $post->getAuthor()->getName(),
                'link' => 'https://example.com/posts/' . $post->getSlug(),
                'pubdate' => $post->getCreatedAt()->format('c'),
                'description' => $post->getExcerpt()
            ]);
        }

        return $feed->render($format);
    }
}

Testing Feed Controllers

Example test for your feed controller:

<?php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use App\Entity\Post;
use App\Entity\User;

class FeedControllerTest extends WebTestCase
{
    public function testPostsFeedReturnsValidXml(): void
    {
        $client = static::createClient();

        // Create test data
        $entityManager = $client->getContainer()->get('doctrine')->getManager();
        
        $author = new User();
        $author->setName('Test Author');
        $entityManager->persist($author);

        $post = new Post();
        $post->setTitle('Test Post');
        $post->setContent('Test content');
        $post->setAuthor($author);
        $post->setPublished(true);
        $entityManager->persist($post);
        
        $entityManager->flush();

        // Request feed
        $crawler = $client->request('GET', '/feed');

        $this->assertResponseIsSuccessful();
        $this->assertResponseHeaderSame('content-type', 'application/rss+xml; charset=utf-8');
        
        // Verify XML structure
        $xml = simplexml_load_string($client->getResponse()->getContent());
        $this->assertNotFalse($xml);
        $this->assertEquals('rss', $xml->getName());
        $this->assertGreaterThan(0, count($xml->channel->item));
    }
}

Configuration Parameters

Add feed-related parameters to your config/services.yaml:

# config/services.yaml
parameters:
    app.name: 'My Symfony Blog'
    app.description: 'A blog about Symfony and PHP'
    app.url: 'https://myblog.com'
    
services:
    # ... other services

Next Steps

Clone this wiki locally