Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Feed Laravel Package

laminas/laminas-feed

laminas-feed is a PHP library for creating, reading, and validating RSS and Atom feeds. It offers parsers, writers, and feed classes with support for extensions like Dublin Core, iTunes, and Podcast, plus HTTP client integration for importing remote feeds.

View on GitHub
Deep Wiki
Context7

Getting Started

laminas/laminas-feed is a framework-agnostic RSS/Atom reader and writer. For a Laravel app, treat it as a low-level feed XML library: you still own routing, caching, queuing, validation, and persistence.

Minimal setup:

composer require laminas/laminas-feed

There is no Laravel service provider to install. Look first at:

  • Laminas\Feed\Writer\Feed for generating RSS/Atom.
  • Laminas\Feed\Reader\Reader for consuming feeds.
  • The extension docs for iTunes, PodcastIndex, Dublin Core, Content, etc.
  • The 2.26.x releases if you consume feeds without an XML encoding declaration or use PodcastIndex/Slash extensions.

First useful use case: expose an RSS feed for published Eloquent posts.

use App\Models\Post;
use Illuminate\Support\Str;
use Laminas\Feed\Writer\Feed;

Route::get('/feed', function () {
    $feed = new Feed();
    $feed->setTitle(config('app.name').' Blog');
    $feed->setLink(route('home'));
    $feed->setDescription('Latest published posts');
    $feed->setFeedLink(route('feed.rss'), 'rss');
    $feed->setDateModified(now());
    $feed->setLanguage('en');

    Post::query()
        ->with('author')
        ->whereNotNull('published_at')
        ->latest('published_at')
        ->limit(50)
        ->get()
        ->each(function (Post $post) use ($feed) {
            $entry = $feed->createEntry();
            $entry->setId((string) $post->id);
            $entry->setTitle($post->title);
            $entry->setLink(route('posts.show', $post));
            $entry->setDateCreated($post->published_at);
            $entry->setDateModified($post->updated_at);
            $entry->setDescription(Str::limit(strip_tags($post->body), 300));
            $entry->setContent($post->body);
            $entry->addAuthor([
                'name' => $post->author->name,
                'email' => $post->author->email,
            ]);

            $feed->addEntry($entry);
        });

    return response($feed->export('rss'), 200)
        ->header('Content-Type', 'application/rss+xml; charset=UTF-8');
})->name('feed.rss');

Validate the output with the W3C Feed Validator before shipping. For Atom, use $feed->export('atom') and application/atom+xml.

Implementation Patterns

Generate feeds through a service, not inline controllers. Wrap feed construction in an application service so it can be reused by controllers, console commands, and tests.

final class BlogFeedService
{
    public function rss(): string
    {
        // build and return $feed->export('rss');
    }

    public function atom(): string
    {
        // build and return $feed->export('atom');
    }
}

Cache rendered XML and use HTTP validators. Feed endpoints are read-heavy and change infrequently.

$xml = Cache::remember('feed:rss', now()->addMinutes(10), fn () => $feedService->rss());

return response($xml)
    ->setEtag('"'.sha1($xml).'"')
    ->header('Content-Type', 'application/rss+xml; charset=UTF-8');

Consume external feeds with Laravel’s HTTP client, then Reader::importString(). This keeps Laravel’s timeout, retry, proxy, and test fakes in control.

use Illuminate\Support\Facades\Http;
use Laminas\Feed\Reader\Reader;

$response = Http::timeout(10)
    ->retry(2, 200)
    ->get($feedUrl);

$feed = Reader::importString($response->body());

foreach ($feed as $entry) {
    ExternalPost::updateOrCreate(
        ['guid' => $entry->getId() ?: $entry->getLink()],
        [
            'title' => $entry->getTitle(),
            'url' => $entry->getLink(),
            'summary' => $entry->getDescription(),
            'content' => $entry->getContent(),
            'published_at' => $entry->getDateCreated(),
            'updated_at' => $entry->getDateModified(),
        ]
    );
}

Use Reader::import($url) only for prototypes or trusted internal URLs. For user-supplied URLs, fetch via Http first so you can validate the URL, limit size, set timeouts, and block private network targets.

Treat feed ingestion as a queued/scheduled job. External feeds are slow and unreliable.

Schedule::job(new RefreshExternalFeed($feed))->hourly()->withoutOverlapping();

Store ETag and Last-Modified response headers and send If-None-Match / If-Modified-Since on subsequent fetches where the upstream supports it.

Map RSS and Atom deliberately. RSS and Atom expose different strengths. RSS is broadly compatible and simple; Atom is better for IDs, updated timestamps, and richer content. Use export('rss') and export('atom') from the same service if you need both, and set Content-Type correctly.

Use absolute URLs everywhere. Feed readers do not reliably resolve relative links or relative image URLs. Use route() or url() for entry links, enclosure URLs, images, and self-links.

For podcasts, set enclosures explicitly. Podcast feeds usually fail because the enclosure is missing or malformed.

$entry->setEnclosure([
    'uri' => $episode->audio_url,
    'type' => 'audio/mpeg',
    'length' => $episode->audio_size_in_bytes,
]);

The iTunes and PodcastIndex extensions are first-class in recent versions. Check the extension docs before hand-writing namespaced XML.

Normalize and persist consumed entries idempotently. Use the entry ID or link as a stable key. Do not assume all feeds provide both.

$key = $entry->getId() ?: $entry->getLink();

Test with fixtures and Laravel fakes. For the writer, parse $feed->export('rss') with simplexml_load_string() and assert node values. For the reader, use Http::fake() plus saved feed XML responses.

Gotchas and Tips

  • Do not pass arbitrary URLs to Reader::import(). It can become an SSRF vector. Validate scheme and host, block localhost/private IP ranges, set timeouts, limit response size, and prefer Http::get() + Reader::importString().
  • XML parsing of untrusted feeds needs limits. Cap fetch size, number of entries processed, and content field size. Process large imports in a queue, not in a web request.
  • Ensure UTF-8 before writing. Recent releases fixed a DOM encoding issue when an XML declaration omits encoding, but your database strings and rendered HTML still need to be valid UTF-8. Strip invalid XML control characters before passing text to the writer.
  • Do not pre-escape content. Pass HTML to setContent() as-is; the writer handles XML escaping. Pre-escaping causes double-encoded entities. Use strip_tags() for short descriptions when appropriate.
  • RSS requires channel title, link, and description. Entries generally need either a title or description. Atom entries require IDs and updated timestamps, so set setId() and date fields explicitly.
  • RSS author elements are email-oriented. addAuthor(['name' => ..., 'email' => ...]) is safest when producing RSS. Missing required author fields can produce invalid feeds.
  • Reader getters return null or throw. getContent(), getEnclosure(), and date getters can be null depending on the feed. Invalid date formats can throw exceptions since 2.13.1, so wrap ingestion in try/catch and log the feed URL.
  • Relative links are not magically resolved. Resolve entry links, image URLs, and enclosure paths against the feed link or XML base yourself.
  • Feed endpoints are often hit by bots and validators. Cache XML, set a proper content type, and avoid loading every model. A feed should contain the latest N entries, not your whole table.
  • Upgrade for relevant fixes. 2.26.1 fixes DOM encoding when the XML declaration lacks encoding. 2.26.3 fixes the Slash extension forcing zero comments onto RSS items. PodcastIndex support has expanded across 2.24–2.26 releases.
  • Extension support is not universal. PodcastIndex, iTunes, Dublin Core, and Slash are supported in varying degrees. Verify the exact extension API in the docs and validate output; do not assume every podcast namespace element is covered.
  • PubSubHubbub and real-time notifications need infrastructure. If you publish feeds for hubs, treat hub notification as a queued side effect with retries. Do not block feed rendering on hub calls.
  • Pin versions in production. This package is mature but has had compatibility fixes around PHP versions, PSR containers, and DOM encoding. Run feed generation and ingestion tests after upgrades.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc