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

Fastfeed Bundle Laravel Package

fastfeed/fastfeed-bundle

Symfony bundle wrapping the FastFeed library for fast, simple RSS and Atom handling. Parse incoming feeds, build and serve your own, and manage feed items through a lightweight API, making it easy to add syndication to Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and check compatibility first

composer require fastfeed/fastfeed-bundle

Before writing a line of code, open vendor/fastfeed/fastfeed-bundle/composer.json and read the require block. This bundle is archived and was written against the Symfony 2.x era, so it will almost certainly list constraints like symfony/framework-bundle: ~2.3 and php: >=5.4. If you are on Symfony 5/6/7 you will get a Composer solver conflict; you have three options, in order of preference:

  1. Don't use it — see the alternative in Gotchas.
  2. Fork it, bump the constraints, and require your fork via a VCS repository.
  3. composer require --ignore-platform-reqs (fragile; the bundle will load but any ContainerAware, getDefinition, or DefinitionDecorator-style API it uses may throw at container compile time).

2. Register the bundle

There is no Flex recipe. Add it manually:

// config/bundles.php  (Symfony 4+)
return [
    // ...
    FastFeed\Bundle\FastFeedBundle::class => ['all' => true],
];

3. Where to look first

Two files tell you everything about how to use this bundle:

  • DependencyInjection/FastFeedExtension.php — reveals the config tree (usually just reader/parser toggles and maybe a cache service).
  • Resources/config/services.xml (or .yml) — reveals the service IDs you will inject. Expect something like fastfeed.aggregator, fastfeed.reader.rss, fastfeed.parser.

Run bin/console debug:container fastfeed --show-private to confirm the real IDs and their class names before guessing.

4. First use case: fetch one feed

The bundle's whole value proposition is that it wires FastFeed's Aggregator for you. Inject it and go:

use FastFeed\Aggregator;

final class FeedPreviewCommand extends Command
{
    protected static $defaultName = 'app:feed:preview';

    public function __construct(private Aggregator $aggregator)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $feed = $this->aggregator->getFeed('https://example.com/rss.xml');

        foreach ($feed->getItems() as $item) {
            $output->writeln(sprintf('%s — %s', $item->getName(), $item->getLink()));
        }

        return Command::SUCCESS;
    }
}

If the service ID is not autowirable (likely, given its age), alias it explicitly:

# config/services.yaml
services:
    FastFeed\Aggregator:
        alias: fastfeed.aggregator

That is the single most useful line you will write for this bundle: it converts an old string-ID service into something modern Symfony autowiring can consume.

Implementation Patterns

Wrap the Aggregator, don't sprinkle it

Do not inject Aggregator into controllers or message handlers directly. The library has no caching, no retry, no logging, and no timeout policy. Create one thin application service that owns those concerns:

final class FeedFetcher
{
    public function __construct(
        private Aggregator $aggregator,
        private CacheInterface $cache,
        private LoggerInterface $logger,
    ) {}

    /** @return iterable<\FastFeed\Item> */
    public function fetch(string $url): iterable
    {
        $key = 'feed.'.sha1($url);

        return $this->cache->get($key, function (ItemInterface $item) use ($url) {
            $item->expiresAfter(900);

            try {
                return $this->aggregator->getFeed($url)->getItems();
            } catch (\Throwable $e) {
                $this->logger->warning('Feed fetch failed', ['url' => $url, 'e' => $e]);
                return [];
            }
        });
    }
}

Feed sources are slow, flaky, and rate-limited by third parties. Caching in front of the aggregator is not an optimization here, it is a correctness requirement.

Poll → dedupe → dispatch, as three separate steps

The pattern that scales is: a console command iterates over a table of feed subscriptions, calls FeedFetcher, and for each Item computes a stable identity (getGuid() if present, else the link URL, else a hash of name + date) and inserts into a feed_item table with a unique constraint. Only newly inserted rows get dispatched as a Messenger message:

// inside the polling command
$conn->transactional(function () use ($items, $bus, $source) {
    foreach ($items as $item) {
        if (!$this->repository->existsByExternalId($item->getGuid() ?? $item->getLink())) {
            $this->repository->persist(FeedItemRecord::fromFastFeed($item, $source));
            $bus->dispatch(new IngestFeedItem($item->getLink()));
        }
    }
});

The unique-constraint-plus-swallowed-violation approach is more robust than a pre-check under concurrency; use it if the poller can run in parallel.

Map to domain immediately

FastFeed's Item is a mutable value object exposing array-access and magic getters. Do not let it leak past your fetch layer — the moment you upgrade or replace the library (likely, given the archive status), every consumer breaks. Convert to a small readonly DTO in FeedFetcher::fetch().

Register custom readers/parsers via the container, not by new

If the bundle exposes a compiler pass or a tagged-service convention for adding ReaderInterface / ParserInterface implementations (check for addCompilerPass in FastFeedBundle::build()), use it — that is the intended extension point. If it does not, define your own compiler pass adding method calls to the fastfeed.aggregator definition:

$container->getDefinition('fastfeed.aggregator')
    ->addMethodCall('addReader', [new Reference(MyReader::class)]);

Testing

Because the aggregator is a normal service, override it in config/services_test.yaml with a stub returning fixture Item objects. That keeps every downstream test offline and fast, which matters a lot here since the library's HTTP layer predates HttpClient mocks.

Gotchas and Tips

The archive status is the headline. No security patches, no PHP 8.2+ fixes, no Symfony compatibility work — ever. Treat the dependency as frozen code you now own. Budget for a fork on day one rather than discovering the problem at your next Symfony major upgrade. For greenfield work, use debril/rss-atom-bundle or laminas/laminas-feed instead; they are maintained and handle the same problem space with better modern Symfony integration.

HTTP behaviour is inherited from the library, not Symfony. FastFeed does its own fetching. That means no framework.http_client defaults, no proxy env-var support, no PSR-18 client, and possibly no configurable timeout. Verify by reading the reader class. If there is no timeout, a single dead feed host will hang your poller — isolate fetches in Messenger workers with your own pcntl_alarm/SIGALRM guard or a Process wrapper, and cap the batch size per run.

Service IDs are string-based and private-by-default on modern Symfony. The classic failure is ServiceNotFoundException: fastfeed.aggregator — usually because the ID is actually fastfeed or fastfeed.aggregator.default. Always resolve via debug:container rather than copying IDs from a nine-year-old README. Then alias to the class name so your code never depends on the ID again.

Encoding and date parsing are the two most common data bugs. Feeds in the wild are frequently non-UTF-8 (Windows-1252, GB2312) and carry malformed RFC 822 dates. Sanitize at the boundary: run mb_convert_encoding($name, 'UTF-8', 'auto') and new \DateTimeImmutable($item->getDate() ?: 'now') inside a try/catch, and persist the raw payload alongside the parsed fields so you can re-parse without re-fetching when a bug shows up.

Debugging tips. bin/console debug:container --tag=fastfeed and debug:container --show-private fastfeed reveal what the extension actually registered. If the bundle fails at compile time with a DefinitionDecorator or ContainerAware error, you are on a Symfony version the bundle predates — stop there and fork rather than patching the vendor directory.

Config quirks. The extension likely has no semantic config at all (many bundles of this vintage just register services unconditionally). If config/packages/fastfeed.yaml gives "unrecognized option", that is expected — you configure by overriding service definitions, not by extension config.

Extension points, in order of preference: (1) alias/override the aggregator service definition, (2) your own compiler pass to add readers/parsers, (3) decorate the aggregator in your own class and alias the original ID to your decorator so third-party code still works. Avoid subclassing the bundle's classes; there is no upstream to merge your fixes into.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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