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.
Architecture fit. laminas/laminas-feed is a framework-agnostic Laminas component, not a Laravel package. It ships no service provider, no facade, no auto-discovery, and no config file, so adopting it in a Laravel codebase means owning a thin adapter layer ourselves. That is acceptable — the component does exactly one thing (parse and build RSS 0.9x/1.0/2.0 and Atom) and does it with a clean object model — but it must be planned as a wrapper effort, not a drop-in install. The good news is that its two halves have very different integration profiles: the Writer is effectively a self-contained XML builder (export() returns a string), and the Reader can consume a raw string via Reader::importString(), which lets us keep Laravel's HTTP client as the network layer instead of adopting laminas-http.
Integration feasibility. Feasible in a 1–2 sprint window for a first useful slice. The main friction points are:
psr/container:^2), so Laravel's container can bridge them, but they are not wired to the app container by default — we register instances explicitly in our own provider.laminas-stdlib, laminas-escaper, laminas-uri, laminas-validator, psr/container, and likely laminas-servicemanager. Not heavy, but it is a second DI paradigm sitting inside a Laravel app, and it will show up in audits and container-size conversations.Feed::make()) we build and maintain.Technical risk. Rated medium, with two genuinely material items:
LIBXML_NONET, external entity resolution disabled, expansion limits) rather than assume them. Feed payload size caps and parse timeouts belong in our code, not the library's. This is the single item I would not sign off without a written test case.Secondary risks: patch-level releases have carried compatibility adjustments before (2.18.1 was tagged as both bug and BC break), so we pin with ^2.26 and treat minors as reviewable, not automatic; and real-world feed malformation is a live source of bugs (2.26.1 fixed a UTF-8 encoding fallback, 2.13.1 changed invalid-date handling to throw an exception rather than return null) — these are exactly the edge cases our own consumers will hit.
Metadata caveats to validate before committing. Two fields in the supplied assessment look unreliable and should be re-checked against Packagist and GitHub directly: Dependents: 0 (implausible for a package that is the maintained successor to zendframework/zend-feed — likely a re-namespaced-package artifact of the data source), and the 2026-10-03 last-release date. I would not present either to stakeholders as-is. The opportunity: 70.3 score, if it means "room for an opinionated Laravel wrapper," matches my own read.
Key questions for the team before build:
spatie/laravel-feed and is close to a differentiator. If no, the competitor is simpler and better known to Laravel devs.Stack fit. Requires PHP 8.1+ in practice (7.3 support was dropped at 2.18.0), which is compatible with Laravel 10/11 baselines. Install as laminas/laminas-feed:^2.26. Confirm "type": "library" in its composer manifest — it will not be auto-discovered, and that is intentional on our side: we want explicit registration, not magic.
Recommended shape. Build a small internal package (acme/laravel-feed) that owns the contract and keeps Laminas\Feed an implementation detail:
FeedServiceProvider registering singletons for the Reader/Writer entry points and the relevant plugin managers, with Laravel's container injected as the PSR-11 delegate.config/feed.php: HTTP timeout, connect timeout, User-Agent, max payload bytes, per-host rate limit, cache TTL, allowed content types, sanitizer profile.FeedResponseFactory: wraps the Writer's export() output into an Illuminate\Http\Response with the correct Content-Type (application/rss+xml; charset=UTF-8 / application/atom+xml), correct cache headers, and ETag/Last-Modified.FeedFetcher: uses Http::withHeaders([...])->timeout(...)->get() then hands the body to Reader::importString(). This is the key architectural choice — it preserves Laravel's retry, middleware, telemetry, and test fakes, and avoids inheriting laminas-http semantics for our outbound traffic.FeedEntry DTO mapping Laminas' entry objects to a stable, narrow shape (id, title, url, published, summary, content, enclosure) so that swapping the parser later is a contained change.Feed::fake() equivalent for tests, backed by a fixture corpus.Migration path. Two likely entry points:
spatie/laravel-feed / laravelium — migrate read paths first behind the DTO. Expect differences in HTML content sanitization, date parsing strictness, and how Atom <content> vs <summary> is selected (upstream fixed an Atom content regex as far back as 2.12.1). Migrate writers second; that is where namespace extensions give us a genuine capability upgrade and where output diffing against the legacy feed is a cheap, high-value validation step.Compatibility notes.
Illuminate\Contracts\Container\Container-native, so avoid injecting Laminas internals into application code.laminas-servicemanager will likely be present in vendor/ regardless of our preference. If its presence is a hard blocker for platform review, the fallback is a SimplePie-based build with reduced podcast capability — flag that tradeoff early rather than at the end.laminas-http conditional GET (Reader::import($uri, $etag, $lastModified)); implement conditional requests with Laravel's client so all outbound HTTP traverses one observable path.Sequencing (4 phases, ~4–6 weeks for one engineer):
FeedFetcher with conditional requests and caching, FeedResponseFactory, FeedEntry DTO, test fakes.Maintenance. Ongoing cost is moderate but bounded, and mostly ours rather than upstream's. Upstream release cadence is healthy (~3 minor releases and 3 patches across the 2.24–2.26 window shown), so expect a handful of dependency bumps per year. Budget roughly 1–2 engineer-days per quarter for version tracking plus a recurring review of Laminas security advisories and their transitive dependencies (laminas-uri, laminas-validator have their own advisory history). The wrapper is the mitigation: if Laminas stalls or a serious advisory lands, we replace the internals behind FeedEntry/FeedResponseFactory without touching product code. I would treat that swap-ability as a hard acceptance criterion, not a nice-to-have.
Support. Documentation is genuinely good (docs.laminas.dev) — better than most packages in this niche — and issues get addressed (the PodcastIndex thread shows maintainers shipping community-requested extension work incrementally). But the support channel is GitHub issues on a volunteer schedule, with no SLA and thin Stack Overflow coverage for the Laminas namespace specifically. Internal knowledge will concentrate in whoever writes the adapter. Mitigations: the fixture corpus as executable documentation, a short internal runbook covering the top five failure modes, and a mandatory second reviewer on the wrapper package.
Scaling. Writer path scales trivially — it is string assembly, cacheable at the edge, no concern. Reader path is where the cost lives: DOM parsing is CPU- and memory-bound, and a 5 MB podcast feed with hundreds of entries is a real workload. Guidance:
ETag/Last-Modified so most polls are 304s.Failure modes to design for explicitly:
| Failure | Symptom | Mitigation |
|---|---|---|
| Malformed XML | Parser exception | Catch, classify, serve stale cache, alert on rate |
| Encoded/declared encoding mismatch | Garbled text (see 2.26.1 fix) | Pin UTF-8 handling; test non-UTF-8 corpus |
| Invalid date in entry | Exception thrown, not null (behavior since 2.13.1) | Per-entry try/catch; never fail the whole feed for one bad item |
| Oversized payload / entity expansion | Memory or CPU exhaustion | Hard byte cap, entity loading disabled, timeout |
| Redirect chains / 404 / 410 | Silent staleness | Explicit status handling; deactivate after N consecutive failures |
| Publisher schema drift | Missing fields, truncated content | DTO defaults; monitor field-null rates |
| Extension namespace not registered | Fields silently absent | Assert on expected namespaces in tests and at runtime |
The "silently absent" class is the one I would emphasize: extension and namespace handling failing quietly is worse than throwing, because the product degrades without an alert. Instrument entry-count and field-population metrics per feed so degradation is visible.
Ramp-up. A Laravel developer new to Laminas needs roughly 1–2 days to be productive with the Reader/Writer object model, and closer to a week to be confident with plugin manager and extension registration — that is the genuinely unfamiliar part. Budget a day of pairing for the first person outside the adapter's author who has to touch it, and make sure at least two engineers can extend the fixture corpus. Compared with SimplePie (a weekend) or spatie/laravel-feed (an afternoon), this is a real but modest onboarding tax — justified only if podcast extensions or strict Atom fidelity are actually on the roadmap. If they are not, the honest recommendation is that this library is technically sound but operationally over-priced for what we would use it for, and a lighter alternative should win on total cost of ownership.
How can I help you explore Laravel packages today?