Installation:
composer require eko/feedbundle
Register the bundle in config/bundles.php:
Eko\FeedBundle\EkoFeedBundle::class => ['all' => true],
Configure Feed:
Create config/packages/eko_feed.yml:
eko_feed:
feeds:
article:
title: 'My Articles'
description: 'Latest articles'
link: 'https://example.com'
encoding: 'utf-8'
author: 'Author Name' # Required for Atom
Implement ItemInterface:
Modify your entity (e.g., Article) to implement ItemInterface:
class Article implements ItemInterface {
public function getFeedItemTitle() { ... }
public function getFeedItemDescription() { ... }
public function getFeedItemPubDate() { ... }
public function getFeedItemLink() { ... }
}
Generate Feed in Controller:
use Eko\FeedBundle\Feed\FeedManager;
class BlogController {
public function __construct(private FeedManager $feedManager) {}
public function feed() {
$articles = $this->getDoctrine()->getRepository(Article::class)->findAll();
$feed = $this->feedManager->get('article');
$feed->addFromArray($articles);
return new Response($feed->render('rss'));
}
}
Create an RSS feed endpoint for blog posts:
# routes.yaml
app_feed:
path: /feed.rss
controller: App\Controller\BlogController::feed
Dynamic Feed Generation:
Use FeedManager to fetch feeds dynamically in controllers:
$feed = $this->feedManager->get('article');
$feed->addFromArray($this->getLatestArticles());
return new Response($feed->render('atom'));
Caching Feeds: Cache the rendered feed for performance:
$cache = $this->get('cache.app');
$cacheKey = 'feed_article_rss';
if (!$feedContent = $cache->get($cacheKey)) {
$feedContent = $feed->render('rss');
$cache->set($cacheKey, $feedContent, 3600); // Cache for 1 hour
}
return new Response($feedContent);
Conditional Feed Rendering: Render different formats based on request:
$format = $request->query->get('format', 'rss');
return new Response($feed->render($format));
Doctrine QueryBuilder:
Optimize feed queries with QueryBuilder:
$qb = $this->createQueryBuilder('a')
->where('a.published = :published')
->setParameter('published', true)
->orderBy('a.createdAt', 'DESC')
->setMaxResults(20);
$feed->addFromArray($qb->getQuery()->getResult());
Translation: Use Symfony’s translator for feed titles/descriptions:
eko_feed:
translation_domain: 'blog'
public function getFeedItemTitle() {
return $this->translator->trans('article.title');
}
Event Listeners: Auto-generate feeds on entity updates:
// src/EventSubscriber/FeedSubscriber.php
class FeedSubscriber implements EventSubscriberInterface {
public function onArticleUpdate(ArticleEvent $event) {
$feed = $this->feedManager->get('article');
$feed->add($event->getArticle());
// Cache or trigger a rebuild
}
}
API-Driven Feeds: Fetch feed items from an external API:
$client = $this->get('http_client');
$response = $client->request('GET', 'https://api.example.com/articles');
$articles = json_decode($response->getContent(), true);
$feed->addFromArray($articles); // Requires custom hydrator
Route-Based Links:
If using RoutedItemInterface, ensure route parameters are correct:
public function getFeedItemRouteParameters() {
return ['slug' => $this->slug]; // Must match route requirements
}
Error: No route found for "..." if parameters are missing.
Date Formatting:
getFeedItemPubDate() must return a DateTime object or ISO-8601 string:
public function getFeedItemPubDate() {
return $this->createdAt->format('c'); // ISO format
}
Error: Invalid XML if date is malformed.
Caching Headers:
Always set Cache-Control headers for feeds:
$response = new Response($feed->render('rss'));
$response->setPublic()
->setMaxAge(3600)
->setSharedMaxAge(3600);
Entity Hydration:
Custom hydrators must implement Eko\FeedBundle\Hydrator\HydratorInterface:
class CustomHydrator implements HydratorInterface {
public function hydrate(array $data, $entity) { ... }
}
Error: Class not found if interface is missing.
Validate XML:
Use DOMDocument to check feed validity:
$dom = new DOMDocument();
$dom->loadXML($feed->render('rss'));
if ($dom->schemaValidate('feed.xsd')) {
// Valid
}
Log Feed Generation: Debug feed content before rendering:
$feedContent = $feed->render('rss');
$this->logger->debug('Feed content:', ['content' => $feedContent]);
Check Configuration:
Validate eko_feed.yml syntax:
php bin/console debug:config eko_feed
Custom Formatters:
Extend Eko\FeedBundle\Feed\Formatter\AbstractFormatter:
class JsonFormatter extends AbstractFormatter {
public function render() {
return json_encode($this->feed->toArray());
}
}
Register in services.yaml:
services:
App\Feed\JsonFormatter:
tags:
- { name: eko_feed.formatter, format: json }
Dynamic Feed Names: Use a service to resolve feed names dynamically:
$feedName = $this->feedNameResolver->resolve($request->get('type'));
$feed = $this->feedManager->get($feedName);
Feed Validation: Add pre-render validation:
$feed->addPreRenderListener(function ($feed) {
if (empty($feed->getItems())) {
throw new \RuntimeException('No items in feed');
}
});
Media Handling:
Use MediaItemField for dynamic media:
public function getFeedMediaItem() {
return [
'type' => $this->getMediaType(),
'length' => filesize($this->getMediaPath()),
'value' => $this->getMediaUrl(),
];
}
Use Console Command for Testing: Dump feeds locally for debugging:
php bin/console eko:feed:dump --name=article --filename=debug.xml
Leverage Groups for Complex Data:
Organize nested data with GroupItemField:
$feed->addItemField(
new GroupItemField('author', [
new ItemField('name', 'getAuthorName'),
new ItemField('role', 'getAuthorRole'),
])
);
Symfony 6+ Compatibility:
Use autowiring for FeedManager:
public function __construct(private FeedManager $feedManager) {}
Performance:
Limit feed items with setMaxItems():
$feed->setMaxItems(10); // Only 10 most recent items
Translation Fallback: Configure fallback translation domains:
eko_feed:
translation_domain: 'blog'
fallback_domain: 'messages'
How can I help you explore Laravel packages today?