Updating Feedamic RSS Feeds to ver 3

A major improvement to our Statamic Blog

IMPORTANT NOTE: Your mileage may very, but in our case, updating from Feedamic from version 2 - 3 WAS NOT PAINLESS. Our feeds broke and getting it to work again required a bit of work. However, the end result is a much better system. So if you find yourself in a similiar situation, maybe this will help you. Now there is a page on Feedamic documentation site that covers updating: https://docs.mity.com.au/feedamic/3/upgrade but for us, our site required a bit more work than what is covered there. So with all that being said...

Welcome to our comprehensive guide on implementing and maintaining RSS and Atom feeds using Feedamic - the powerful feed generation package for Statamic CMS.

Feedamic has become an essential tool for content syndication, allowing our Laravel application to generate clean, standards-compliant RSS and Atom feeds that can be consumed by feed readers, social media platforms, and other applications. Whether you're looking to set up feeds for the first time or maintain an existing implementation, this guide covers everything from basic configuration to advanced troubleshooting.

The Feedamic package provides a robust foundation for feed generation, but as we discovered during our upgrade to version 3.0.1, it sometimes requires custom solutions to handle edge cases and ensure optimal performance. This documentation captures our complete implementation, including the custom controller and middleware system we developed to overcome XML corruption issues and improve reliability.

In this guide, you'll find detailed explanations of our feed architecture, complete source code examples, and a comprehensive walkthrough of our upgrade process from Feedamic 2.x to 3.0.1. We've documented every change, every challenge we faced, and every solution we implemented to help you maintain a robust feeds system.

Import Parts

Here is a quick / brief overview of the different, important, elements required to get our RSS feeds to populate correctly.

1. Feedamic Package

  • Package: mitydigital/feedamic (version 3.0.1)
  • Purpose: Core feed generation functionality
  • Configuration: Managed through resources/addons/feedamic.yaml

2. Custom Feed Controller

  • File: app/Http/Controllers/CustomFeedController.php
  • Purpose: Bypasses problematic XML processing in Feedamic package
  • Routes: Handles /feed/atom and /feed endpoints

3. Middleware System

  • File: app/Http/Middleware/InterceptFeedamic.php
  • Purpose: Intercepts feed routes and redirects to custom controller
  • Integration: Registered in app/Http/Kernel.php

4. Error Suppression (IMPORTANT)

  • File: public/index.php
  • Purpose: Prevents deprecation warnings from corrupting XML output
  • Scope: Applied specifically to feed routes

PART 1 | Feedamic Package + Config

Some of the main things that are set up with the new feedamic.yaml file are the same as when you were using the old config/feedamic.php file.

  1. Routes: Define the URL endpoints for feeds

    • Atom feed: /feed/atom
    • RSS feed: /feed
  2. Collections: Specify which content collections to include

    • blog: Main blog posts
    • interviews: Interview content
  3. Content Mapping: Define how content fields are mapped to feed elements

    • Summary: Uses introduction, notes, and content fields
    • Content: Uses introduction, content, and transcript fields
  4. Author Configuration: Define how author information is handled

    • Model: MityDigital\Feedamic\Models\FeedamicAuthor
    • Fallback: Huement.com Blog with email public@huement.com

PART 2 | Custom Feed Controller

File: app/Http/Controllers/CustomFeedController.php

<?php

namespace App\Http\Controllers;

use Carbon\Carbon; use Illuminate\Http\Response; use MityDigital\Feedamic\Facades\Feedamic; use Statamic\Facades\Site;

class CustomFeedController extends Controller { public function atom() { // Suppress all errors to prevent XML corruption $oldErrorReporting = error_reporting(0); ini_set('display_errors', 0); ini_set('log_errors', 0);

// Set a custom error handler to completely suppress warnings
set_error_handler(function ($severity, $message, $file, $line) {
  return true; // Suppress all errors
});

try {
  \Log::info('CustomFeedController: atom() method called');
  $config = Feedamic::getConfig('/feed/atom', 'default');
  if (!$config) {
    \Log::info('CustomFeedController: No config found for /feed/atom');
    abort(404);
  }

  $entries = Feedamic::getEntries($config);

  // Generate XML directly without XMLReader/XMLWriter processing
  $xml = view('feedamic::atom', [
    'id' =&gt; request()-&gt;url(),
    'config' =&gt; $config,
    'entries' =&gt; $entries,
    'site' =&gt; Site::current(),
    'updated' =&gt; Carbon::now(),
    'url' =&gt; request()-&gt;url(),
  ])-&gt;render();

  // Clean up any XML corruption from deprecation warnings
  $xml = preg_replace('/^[^&lt;]*/', '', $xml); // Remove anything before the first &lt;
  $xml = trim($xml);

  return response($xml, 200, [
    'Content-Type' =&gt; 'application/xml; charset=utf-8',
    'Cache-Control' =&gt; 'public, max-age=3600',
  ]);
} catch (\Exception $e) {
  // Return a basic feed structure if there's an error
  $basicFeed =
    '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"> <id>' . request()->url() . '</id> <title type="text">Huement Blog Main Feed</title> <subtitle type="text">Feed temporarily unavailable</subtitle> <updated>' . Carbon::now()->toRfc3339String() . '</updated> </feed>';

  return response($basicFeed, 200, [
    'Content-Type' =&gt; 'application/xml; charset=utf-8',
  ]);
} finally {
  // Restore original error reporting
  error_reporting($oldErrorReporting);
  ini_set('display_errors', 1);
  ini_set('log_errors', 1);
  restore_error_handler();
}

}

public function rss() { // Suppress all errors to prevent XML corruption $oldErrorReporting = error_reporting(0); ini_set('display_errors', 0); ini_set('log_errors', 0);

// Set a custom error handler to completely suppress warnings
set_error_handler(function ($severity, $message, $file, $line) {
  return true; // Suppress all errors
});

try {
  $config = Feedamic::getConfig('/feed', 'default');
  if (!$config) {
    abort(404);
  }

  $entries = Feedamic::getEntries($config);

  // Generate XML directly without XMLReader/XMLWriter processing
  $xml = view('feedamic::rss', [
    'id' =&gt; request()-&gt;url(),
    'config' =&gt; $config,
    'entries' =&gt; $entries,
    'site' =&gt; Site::current(),
    'updated' =&gt; Carbon::now(),
    'url' =&gt; request()-&gt;url(),
  ])-&gt;render();

  return response($xml, 200, [
    'Content-Type' =&gt; 'application/rss+xml; charset=utf-8',
    'Cache-Control' =&gt; 'public, max-age=3600',
  ]);
} catch (\Exception $e) {
  // Return a basic RSS structure if there's an error
  $basicFeed =
    '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

<rss version="2.0"> <channel> <title>Huement Blog Main Feed</title> <description>Feed temporarily unavailable</description> <link>' . config('app.url') . '</link> <lastBuildDate>' . Carbon::now()->toRfc2822String() . '</lastBuildDate> </channel> </rss>';

  return response($basicFeed, 200, [
    'Content-Type' =&gt; 'application/rss+xml; charset=utf-8',
  ]);
} finally {
  // Restore original error reporting
  error_reporting($oldErrorReporting);
  ini_set('display_errors', 1);
  ini_set('log_errors', 1);
  restore_error_handler();
}

} }

Key Features

  1. Error Suppression: Prevents deprecation warnings from corrupting XML output
  2. Direct XML Generation: Bypasses problematic XMLReader/XMLWriter processing
  3. Fallback Handling: Provides basic feed structure if errors occur
  4. Proper Headers: Sets correct Content-Type and caching headers
  5. Logging: Includes debug logging for troubleshooting

PART 3 | Middleware System

File: app/Http/Middleware/InterceptFeedamic.php

<?php

namespace App\Http\Middleware;

use App\Http\Controllers\CustomFeedController; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response;

class InterceptFeedamic { /**

  • Handle an incoming request.
  • @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { // Intercept feed routes and redirect to our custom controller if ($request->is('feed/atom') || $request->path() === 'feed/atom') { \Log::info('InterceptFeedamic: Intercepting /feed/atom'); $controller = new CustomFeedController(); return $controller->atom(); }
if ($request-&gt;is('feed') || $request-&gt;path() === 'feed') {
  \Log::info('InterceptFeedamic: Intercepting /feed');
  $controller = new CustomFeedController();
  return $controller-&gt;rss();
}

return $next($request);

} }

Middleware Registration

File: app/Http/Kernel.php

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Spatie\CookieConsent\CookieConsentMiddleware::class,
        \App\Http\Middleware\SetCanonicalUrl::class,
        \App\Http\Middleware\SuppressWarningsForFeeds::class,
        \App\Http\Middleware\InterceptFeedamic::class,
        \Statamic\StaticCaching\Middleware\Cache::class,
    ],
    // ... other middleware groups
];

Part 4 | Error Suppression

This part was solved via Cursor.ai, after a little bit of head scratching. Out of the gate we were getting a lot of errors when we first updated, different errors like: error on line 4 at column 54: xmlParseEntityRef: no name and various XML corruptions causing problems, to fix that we added a special catch for the /feed route.

File: public/index.php

<?php

// Aggressively suppress all errors for feed routes to prevent XML corruption if ( isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/feed') !== false ) { // Completely suppress all errors for feed routes error_reporting(0); ini_set('display_errors', 0); ini_set('log_errors', 0); ini_set('html_errors', 0); // Suppress all errors at the PHP level set_error_handler(function ($severity, $message, $file, $line) { return true; // Suppress all errors for feed routes }); // Also suppress output buffering issues ob_start(); }

use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// ... rest of the file

Comments

No Comments Yet!

Would you like to be the first?

Comment Moderation is ON for this post. All comments must be approved before they will be visible.

Add Comment

SITEMAP MENU