-
Notifications
You must be signed in to change notification settings - Fork 94
Laravel
Rumen Damyanov edited this page Jul 29, 2025
·
1 revision
This guide shows how to use php-feed with Laravel applications.
Add the package to your Laravel project:
composer require rumenx/php-feed
No service provider registration is needed - the package works out of the box!
Create a feed controller to handle your RSS/Atom endpoints:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Controller;
use Illuminate\Http\Response;
use Rumenx\Feed\FeedFactory;
use App\Models\Post;
class FeedController extends Controller
{
public function posts(): Response
{
$feed = FeedFactory::create();
$feed->setTitle(config('app.name') . ' - Latest Posts');
$feed->setDescription('Latest blog posts from ' . config('app.name'));
$feed->setLink(url('/'));
$feed->setLanguage('en');
// Get latest published posts
$posts = Post::published()
->orderBy('created_at', 'desc')
->limit(20)
->get();
foreach ($posts as $post) {
$feed->addItem([
'title' => $post->title,
'author' => $post->author->name,
'link' => route('posts.show', $post->slug),
'pubdate' => $post->created_at,
'description' => $post->excerpt,
'category' => $post->categories->pluck('name')->toArray()
]);
}
return response($feed->render('rss'), 200, [
'Content-Type' => 'application/rss+xml; charset=utf-8'
]);
}
}
Add feed routes to your routes/web.php
:
<?php
use App\Http\Controllers\FeedController;
Route::get('/feed', [FeedController::class, 'posts'])->name('feed.posts');
Route::get('/feed.rss', [FeedController::class, 'posts'])->name('feed.rss');
Route::get('/feed.xml', [FeedController::class, 'posts'])->name('feed.xml');
// Different content types
Route::get('/feed/posts', [FeedController::class, 'posts'])->name('feed.posts');
Route::get('/feed/news', [FeedController::class, 'news'])->name('feed.news');
Here's how to create feeds from different Eloquent models:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Controller;
use Illuminate\Http\Response;
use Rumenx\Feed\FeedFactory;
use App\Models\Post;
use App\Models\News;
use App\Models\Product;
class FeedController extends Controller
{
public function posts(): Response
{
return $this->createFeed(
Post::published()->latest()->limit(20)->get(),
'Blog Posts',
'Latest blog posts',
'posts.show'
);
}
public function news(): Response
{
return $this->createFeed(
News::published()->latest()->limit(20)->get(),
'News Updates',
'Latest news and updates',
'news.show'
);
}
public function products(): Response
{
return $this->createFeed(
Product::available()->latest()->limit(50)->get(),
'New Products',
'Latest products in our store',
'products.show'
);
}
private function createFeed($items, string $title, string $description, string $routeName): Response
{
$feed = FeedFactory::create();
$feed->setTitle(config('app.name') . ' - ' . $title);
$feed->setDescription($description);
$feed->setLink(url('/'));
$feed->setLanguage(app()->getLocale());
foreach ($items as $item) {
$feed->addItem([
'title' => $item->title,
'author' => $item->author->name ?? 'Unknown',
'link' => route($routeName, $item->slug),
'pubdate' => $item->created_at,
'description' => $item->excerpt ?? $item->description ?? str_limit($item->content, 300),
'category' => $item->categories?->pluck('name')->toArray() ?? []
]);
}
return response($feed->render('rss'), 200, [
'Content-Type' => 'application/rss+xml; charset=utf-8'
]);
}
}
Add feed discovery links to your layout:
In your Blade layout (resources/views/layouts/app.blade.php
):
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Feed Discovery -->
<link rel="alternate" type="application/rss+xml" title="{{ config('app.name') }} - RSS Feed" href="{{ route('feed.rss') }}">
<link rel="alternate" type="application/atom+xml" title="{{ config('app.name') }} - Atom Feed" href="{{ route('feed.atom') }}">
</head>
<body>
@yield('content')
</body>
</html>
Or generate links dynamically:
// In a controller or view
use Rumenx\Feed\Feed;
// Generate feed discovery links
echo Feed::link(route('feed.rss'), 'rss', config('app.name') . ' RSS Feed');
echo Feed::link(route('feed.atom'), 'atom', config('app.name') . ' Atom Feed');
Laravel example with Redis caching and error handling:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Controller;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Rumenx\Feed\FeedFactory;
use App\Models\Post;
class FeedController extends Controller
{
public function posts(): Response
{
try {
// Cache feed for 1 hour
$feedXml = Cache::remember('blog-feed-rss', 3600, function () {
return $this->generatePostsFeed();
});
return response($feedXml, 200, [
'Content-Type' => 'application/rss+xml; charset=utf-8',
'Cache-Control' => 'public, max-age=3600'
]);
} catch (\Exception $e) {
Log::error('Feed generation failed: ' . $e->getMessage());
return response('Feed temporarily unavailable', 500);
}
}
private function generatePostsFeed(): string
{
$feed = FeedFactory::create();
$feed->setTitle(config('app.name') . ' - Blog Posts');
$feed->setDescription('Latest posts from our blog');
$feed->setLink(url('/'));
$feed->setLanguage(config('app.locale', 'en'));
$posts = Post::with(['author', 'categories'])
->published()
->orderBy('created_at', 'desc')
->limit(20)
->get();
foreach ($posts as $post) {
$feed->addItem([
'title' => $post->title,
'author' => $post->author->name,
'link' => route('posts.show', $post->slug),
'pubdate' => $post->created_at->toISOString(),
'description' => $post->excerpt,
'category' => $post->categories->pluck('name')->toArray(),
'guid' => route('posts.show', $post->slug)
]);
}
return $feed->render('rss');
}
}
Create specialized feeds for different content types:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Controller;
use Illuminate\Http\Response;
use Rumenx\Feed\FeedFactory;
use App\Models\Post;
class FeedController extends Controller
{
public function technology(): Response
{
return $this->categoryFeed('technology', 'Technology Posts');
}
public function tutorials(): Response
{
return $this->categoryFeed('tutorials', 'Tutorial Posts');
}
public function news(): Response
{
return $this->categoryFeed('news', 'News Updates');
}
private function categoryFeed(string $categorySlug, string $title): Response
{
$feed = FeedFactory::create();
$feed->setTitle(config('app.name') . ' - ' . $title);
$feed->setDescription($title . ' from ' . config('app.name'));
$feed->setLink(url('/category/' . $categorySlug));
$posts = Post::whereHas('categories', function ($query) use ($categorySlug) {
$query->where('slug', $categorySlug);
})
->published()
->latest()
->limit(20)
->get();
foreach ($posts as $post) {
$feed->addItem([
'title' => $post->title,
'author' => $post->author->name,
'link' => route('posts.show', $post->slug),
'pubdate' => $post->created_at,
'description' => $post->excerpt,
'category' => [$title]
]);
}
return response($feed->render('rss'), 200, [
'Content-Type' => 'application/rss+xml; charset=utf-8'
]);
}
}
Create a podcast feed with media enclosures:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Controller;
use Illuminate\Http\Response;
use Rumenx\Feed\FeedFactory;
use App\Models\Episode;
class PodcastController extends Controller
{
public function feed(): Response
{
$feed = FeedFactory::create();
$feed->setTitle('My Podcast');
$feed->setDescription('Weekly episodes about technology and programming');
$feed->setLink(route('podcast.index'));
$feed->setLanguage('en');
$episodes = Episode::published()
->orderBy('published_at', 'desc')
->limit(50)
->get();
foreach ($episodes as $episode) {
$feed->addItem([
'title' => $episode->title,
'author' => 'Podcast Host',
'link' => route('podcast.episode', $episode->slug),
'pubdate' => $episode->published_at,
'description' => $episode->description,
'enclosure' => [
'url' => $episode->audio_url,
'type' => 'audio/mpeg',
'length' => $episode->file_size
]
]);
}
return response($feed->render('rss'), 200, [
'Content-Type' => 'application/rss+xml; charset=utf-8'
]);
}
}
Create an Artisan command to generate static feed files:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Rumenx\Feed\FeedFactory;
use App\Models\Post;
class GenerateFeeds extends Command
{
protected $signature = 'feeds:generate';
protected $description = 'Generate static RSS and Atom feed files';
public function handle(): int
{
$this->info('Generating RSS feeds...');
// Generate RSS feed
$rssFeed = $this->createFeed('rss');
Storage::disk('public')->put('feed.rss', $rssFeed);
// Generate Atom feed
$atomFeed = $this->createFeed('atom');
Storage::disk('public')->put('feed.atom', $atomFeed);
$this->info('Feeds generated successfully!');
return 0;
}
private function createFeed(string $format): string
{
$feed = FeedFactory::create();
$feed->setTitle(config('app.name'));
$feed->setDescription('Latest posts from ' . config('app.name'));
$feed->setLink(url('/'));
$posts = Post::published()->latest()->limit(20)->get();
foreach ($posts as $post) {
$feed->addItem([
'title' => $post->title,
'author' => $post->author->name,
'link' => route('posts.show', $post->slug),
'pubdate' => $post->created_at,
'description' => $post->excerpt
]);
}
return $feed->render($format);
}
}
Example PHPUnit test for your feed controller:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Post;
use App\Models\User;
class FeedControllerTest extends TestCase
{
public function test_rss_feed_returns_valid_xml(): void
{
// Create test data
$author = User::factory()->create();
$posts = Post::factory()->count(5)->create([
'author_id' => $author->id,
'published' => true
]);
// Request feed
$response = $this->get('/feed');
$response->assertStatus(200);
$response->assertHeader('content-type', 'application/rss+xml; charset=utf-8');
// Verify XML structure
$xml = simplexml_load_string($response->getContent());
$this->assertNotFalse($xml);
$this->assertEquals('rss', $xml->getName());
$this->assertCount(5, $xml->channel->item);
}
}
- Learn about Caching strategies
- See Custom Views for custom templates
- Check out Advanced Features for more options