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

Laravel Feed Reader Laravel Package

vedmant/laravel-feed-reader

A Laravel package for fetching and parsing RSS and Atom feeds with a simple, expressive API. Read feed items, access titles, links, descriptions, and metadata, and cache results to keep your application fast and reliable.

Deep Wiki
Context7

Getting Started

Install the package and publish its config so cache lifetime and HTTP settings are editable per environment:

composer require vedmant/laravel-feed-reader
php artisan vendor:publish --provider="Vedmant\FeedReader\FeedReaderServiceProvider"

The facade is the primary entry point and is usually aliased as FeedReader. Confirm the alias in config/app.php (or rely on package auto-discovery) and check config/feed-reader.php for the cache TTL, user agent, and timeout keys before you write any calling code.

Your first use case should be the one the package is built for: a scheduled command or a small service class that pulls a remote feed and returns structured items.

use Vedmant\FeedReader\Facades\FeedReader;

$feed = FeedReader::read('https://example.com/feed.xml');

$feed->title;          // channel-level metadata
$feed->description;
$feed->link;

foreach ($feed->getItems() as $item) {
    $item->title;
    $item->link;
    $item->date;       // Carbon-ready timestamp / date string
    $item->content;    // full HTML body when published
    $item->description;
}

Two things to do on day one:

  1. Verify discovery works, since the package can resolve a feed from a plain site URL. Pass https://example.com instead of the exact feed URL and inspect $feed->link / $feed->title to confirm it followed the <link rel="alternate"> tags rather than returning an empty object.
  2. Read the raw item object once (dd($item) or json_encode) so you know exactly which properties and magic accessors your target feeds actually populate. Fields differ wildly between RSS 1.0/2.0 and Atom.

Implementation Patterns

Wrap it in a service, not a controller. The facade returns objects tied to a single fetch, so keep the fetch boundary in a dedicated class that returns plain arrays or DTOs. Controllers and Blade views should never touch the feed object directly — that makes swapping the parser later trivial.

Cache at the item level, not just the request. The package caches raw responses, but you almost always want to cache your mapped payload too, because mapping (image extraction, date normalization, HTML sanitizing) is the expensive part and is what breaks when a feed changes shape.

class FeedSync
{
    public function items(string $url): array
    {
        return Cache::remember("feed:{$url}", now()->addMinutes(30), function () use ($url) {
            return collect(FeedReader::read($url)->getItems())
                ->map(fn ($item) => [
                    'title'   => (string) $item->title,
                    'link'    => (string) $item->link,
                    'date'    => $item->date ? Carbon::parse($item->date) : null,
                    'summary' => strip_tags((string) $item->description),
                ])
                ->all();
        });
    }
}

Use it as a scheduler job. The canonical workflow is ->daily() (or ->hourly() for news feeds) invoking a job that reads each stored source URL, matches items against a last_seen_link or last_seen_date marker, and dispatches per-item events. Store the newest link or GUID you processed rather than relying on date, because several major publishers reorder or re-publish items.

Leverage discovery for user-submitted URLs. If you accept "add your blog" from users, run discovery first, resolve the concrete feed URL, and persist that alongside the original site URL. Next fetch is then deterministic and saves a round trip.

Fan out with rate limiting. Reading many feeds in one job will hit remote timeouts. Queue one job per feed, add ->onQueue('feeds') with a low retry_after, and throttle the queue worker so you don't burst against the same host. Combine with the package's config timeout and a small sleep if you must loop synchronously.

Normalize before persisting. Item content frequently includes tracking pixels, relative links, and inline styles. Run strip_tags / an HTML sanitizer before saving if the content is ever rendered by your app, and rewrite relative src/href against the feed's site base URL.

Pair with HTTP caching headers. When the package's own cache is cold, it still has to fetch. Use If-Modified-Since/ETag where the underlying cache layer supports it, or simply keep your local TTL shorter than the remote one and accept the extra request — but never set TTL to 0 in production.

Testing: extract the fetch call behind your own interface so tests can inject a fixture string. Parsing a stored XML fixture is fast and deterministic; hitting real URLs in tests is neither.

Gotchas and Tips

Http::fake() often won't intercept these requests. Feed readers commonly use cURL directly or PHP stream wrappers rather than Laravel's HTTP client, so faking Laravel's client can still let a real network call leak into your test suite. Guard by injecting a fixture-based parser or asserting on a local file/Docker stub instead.

Empty feed objects don't throw. A 404, a WAF block page, or a malformed XML response usually parses into an empty feed rather than an exception. Always check title and count($feed->getItems()) before assuming success, and log the URL and HTTP status when both are empty.

Date semantics differ by format. Atom <updated> vs <published> and RSS <pubDate> in RFC 2822 are not interchangeable. Some feeds give timezone-less strings; wrap Carbon::parse() in a try/catch and fall back to now() so a single bad item doesn't kill the whole import.

HTML entities are decoded inconsistently. Titles in particular come through with &amp;, &#8217;, and CDATA remnants depending on format. Run html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8') before storing.

Config changes are not picked up mid-process. Cache TTL and user agent are read at resolve time; clear the cache (php artisan cache:clear or the package's feed-reader.clear-style command, if provided) rather than expecting a fresh config value to take effect in a long-running worker.

Watch encoding. Non-UTF-8 feeds (Windows-1251, ISO-8859-1) can yield mojibake in titles and descriptions while the feed object still reports success. Validate a sample field with mb_check_encoding($value, 'UTF-8') in your import tests.

Discovery is fragile on JS-heavy sites. It follows markup, not rendered DOM. If discovery fails, fall back to the documented feeds and let the user paste a direct URL; surface that fallback in your UI rather than silently importing nothing.

Extension points worth knowing: the published config file controls cache driver/TTL and request headers; the service container binding can be overridden to swap in a decorated reader (useful for adding logging or metrics around every fetch); and the underlying parser generally exposes the raw library object, which is your escape hatch when the fluent API omits a namespaced field like media:content or Atom <link rel="enclosure">.

One final tip: pin the major version and set up a feed fixture test for each distinct source you depend on. Feed formats drift quietly, and a fixture diff will catch a publisher silently changing their schema long before your users notice empty cards.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core