Skip to content

Advanced Features

Rumen Damyanov edited this page Jul 29, 2025 · 1 revision

Advanced Features Examples

This guide covers advanced features and patterns for complex feed scenarios, performance optimization, and enterprise-level implementations.

Feed Aggregation

Combine multiple data sources into a single feed:

<?php
require 'vendor/autoload.php';

use Rumenx\Feed\FeedFactory;

class FeedAggregator
{
    private array $dataSources;
    private int $maxItems;

    public function __construct(array $dataSources, int $maxItems = 50)
    {
        $this->dataSources = $dataSources;
        $this->maxItems = $maxItems;
    }

    public function generateAggregatedFeed(): string
    {
        $feed = FeedFactory::create();
        $feed->setTitle('Aggregated Content Feed');
        $feed->setDescription('Combined content from multiple sources');
        $feed->setLink('https://example.com/aggregated');

        $allItems = [];

        // Collect items from all sources
        foreach ($this->dataSources as $source) {
            $items = $this->fetchFromSource($source);
            $allItems = array_merge($allItems, $items);
        }

        // Sort by publication date (newest first)
        usort($allItems, function ($a, $b) {
            return strtotime($b['pubdate']) <=> strtotime($a['pubdate']);
        });

        // Limit items and add to feed
        $limitedItems = array_slice($allItems, 0, $this->maxItems);

        foreach ($limitedItems as $item) {
            $feed->addItem($item);
        }

        return $feed->render('rss');
    }

    private function fetchFromSource(array $source): array
    {
        switch ($source['type']) {
            case 'database':
                return $this->fetchFromDatabase($source);
            case 'api':
                return $this->fetchFromApi($source);
            case 'rss':
                return $this->fetchFromRssFeed($source);
            case 'json':
                return $this->fetchFromJsonFeed($source);
            default:
                return [];
        }
    }

    private function fetchFromDatabase(array $source): array
    {
        $pdo = new PDO($source['dsn'], $source['username'], $source['password']);
        $stmt = $pdo->prepare($source['query']);
        $stmt->execute($source['params'] ?? []);
        
        $items = [];
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $items[] = [
                'title' => $row['title'],
                'description' => $row['description'],
                'link' => $row['link'],
                'pubdate' => $row['created_at'],
                'author' => $row['author'] ?? null,
                'category' => explode(',', $row['categories'] ?? ''),
                'source' => $source['name']
            ];
        }

        return $items;
    }

    private function fetchFromApi(array $source): array
    {
        $response = file_get_contents($source['url'], false, stream_context_create([
            'http' => [
                'header' => $source['headers'] ?? [],
                'timeout' => $source['timeout'] ?? 30
            ]
        ]));

        if (!$response) {
            return [];
        }

        $data = json_decode($response, true);
        $items = [];

        foreach ($data[$source['items_key']] as $item) {
            $items[] = [
                'title' => $item[$source['mapping']['title']],
                'description' => $item[$source['mapping']['description']],
                'link' => $item[$source['mapping']['link']],
                'pubdate' => $item[$source['mapping']['pubdate']],
                'author' => $item[$source['mapping']['author']] ?? null,
                'source' => $source['name']
            ];
        }

        return $items;
    }

    private function fetchFromRssFeed(array $source): array
    {
        $xml = simplexml_load_string(file_get_contents($source['url']));
        if (!$xml) {
            return [];
        }

        $items = [];
        foreach ($xml->channel->item as $item) {
            $items[] = [
                'title' => (string) $item->title,
                'description' => (string) $item->description,
                'link' => (string) $item->link,
                'pubdate' => (string) $item->pubDate,
                'author' => (string) $item->author,
                'source' => $source['name']
            ];
        }

        return $items;
    }

    private function fetchFromJsonFeed(array $source): array
    {
        $data = json_decode(file_get_contents($source['url']), true);
        if (!$data) {
            return [];
        }

        $items = [];
        foreach ($data['items'] as $item) {
            $items[] = [
                'title' => $item['title'],
                'description' => $item['summary'] ?? $item['content_text'] ?? '',
                'link' => $item['url'],
                'pubdate' => $item['date_published'],
                'author' => $item['authors'][0]['name'] ?? null,
                'source' => $source['name']
            ];
        }

        return $items;
    }
}

// Usage
$aggregator = new FeedAggregator([
    [
        'name' => 'Blog Posts',
        'type' => 'database',
        'dsn' => 'mysql:host=localhost;dbname=blog',
        'username' => 'user',
        'password' => 'pass',
        'query' => 'SELECT * FROM posts WHERE published = 1 ORDER BY created_at DESC LIMIT 20'
    ],
    [
        'name' => 'News API',
        'type' => 'api',
        'url' => 'https://api.example.com/news',
        'headers' => ['Authorization: Bearer ' . $apiKey],
        'items_key' => 'articles',
        'mapping' => [
            'title' => 'headline',
            'description' => 'summary',
            'link' => 'url',
            'pubdate' => 'publishedAt',
            'author' => 'author'
        ]
    ],
    [
        'name' => 'External RSS',
        'type' => 'rss',
        'url' => 'https://external-site.com/feed.xml'
    ]
], 30);

header('Content-Type: application/rss+xml; charset=utf-8');
echo $aggregator->generateAggregatedFeed();

Conditional Content and Filtering

Smart content filtering based on various criteria:

<?php

class ConditionalFeedGenerator
{
    private array $filters;
    private array $contentRules;

    public function __construct(array $filters = [], array $contentRules = [])
    {
        $this->filters = $filters;
        $this->contentRules = $contentRules;
    }

    public function generateFeed(array $baseItems): string
    {
        $feed = FeedFactory::create();
        $feed->setTitle('Smart Filtered Feed');
        $feed->setDescription('Intelligently filtered content');
        $feed->setLink('https://example.com');

        // Apply filters
        $filteredItems = $this->applyFilters($baseItems);

        // Apply content rules
        $processedItems = $this->applyContentRules($filteredItems);

        foreach ($processedItems as $item) {
            $feed->addItem($item);
        }

        return $feed->render('rss');
    }

    private function applyFilters(array $items): array
    {
        foreach ($this->filters as $filter) {
            $items = array_filter($items, function ($item) use ($filter) {
                return $this->evaluateFilter($item, $filter);
            });
        }

        return array_values($items);
    }

    private function evaluateFilter(array $item, array $filter): bool
    {
        switch ($filter['type']) {
            case 'date_range':
                $itemDate = strtotime($item['pubdate']);
                $startDate = strtotime($filter['start']);
                $endDate = strtotime($filter['end']);
                return $itemDate >= $startDate && $itemDate <= $endDate;

            case 'category_include':
                $itemCategories = is_array($item['category']) ? $item['category'] : [$item['category']];
                return !empty(array_intersect($itemCategories, $filter['categories']));

            case 'category_exclude':
                $itemCategories = is_array($item['category']) ? $item['category'] : [$item['category']];
                return empty(array_intersect($itemCategories, $filter['categories']));

            case 'author':
                return in_array($item['author'], $filter['authors']);

            case 'keyword':
                $content = $item['title'] . ' ' . $item['description'];
                foreach ($filter['keywords'] as $keyword) {
                    if (stripos($content, $keyword) !== false) {
                        return $filter['match'] === 'any' ? true : false;
                    }
                }
                return $filter['match'] === 'all';

            case 'content_length':
                $length = strlen(strip_tags($item['description']));
                return $length >= $filter['min'] && $length <= $filter['max'];

            case 'popularity':
                $score = $this->calculatePopularityScore($item);
                return $score >= $filter['min_score'];

            default:
                return true;
        }
    }

    private function applyContentRules(array $items): array
    {
        return array_map(function ($item) {
            foreach ($this->contentRules as $rule) {
                $item = $this->applyContentRule($item, $rule);
            }
            return $item;
        }, $items);
    }

    private function applyContentRule(array $item, array $rule): array
    {
        switch ($rule['type']) {
            case 'truncate_description':
                if (strlen($item['description']) > $rule['max_length']) {
                    $item['description'] = substr($item['description'], 0, $rule['max_length']) . '...';
                }
                break;

            case 'add_prefix':
                if ($this->matchesCondition($item, $rule['condition'])) {
                    $item['title'] = $rule['prefix'] . $item['title'];
                }
                break;

            case 'modify_link':
                if (isset($rule['utm_params'])) {
                    $params = http_build_query($rule['utm_params']);
                    $separator = strpos($item['link'], '?') !== false ? '&' : '?';
                    $item['link'] .= $separator . $params;
                }
                break;

            case 'content_warning':
                if ($this->containsSensitiveContent($item)) {
                    $item['title'] = '[CW] ' . $item['title'];
                    $item['description'] = 'Content warning: ' . $item['description'];
                }
                break;

            case 'language_detection':
                $detectedLang = $this->detectLanguage($item['description']);
                $item['language'] = $detectedLang;
                break;
        }

        return $item;
    }

    private function calculatePopularityScore(array $item): int
    {
        // Simple popularity scoring algorithm
        $score = 0;
        
        // Recency bonus
        $daysSincePublished = (time() - strtotime($item['pubdate'])) / (24 * 60 * 60);
        $score += max(0, 100 - $daysSincePublished * 5);
        
        // Content quality indicators
        if (strlen($item['description']) > 500) $score += 20;
        if (isset($item['image'])) $score += 15;
        if (isset($item['video'])) $score += 25;
        
        // Engagement metrics (if available)
        if (isset($item['comments_count'])) $score += min($item['comments_count'] * 2, 50);
        if (isset($item['shares_count'])) $score += min($item['shares_count'] * 3, 75);
        
        return $score;
    }

    private function matchesCondition(array $item, array $condition): bool
    {
        switch ($condition['field']) {
            case 'category':
                $categories = is_array($item['category']) ? $item['category'] : [$item['category']];
                return in_array($condition['value'], $categories);
            case 'author':
                return $item['author'] === $condition['value'];
            default:
                return false;
        }
    }

    private function containsSensitiveContent(array $item): bool
    {
        $sensitiveWords = ['violence', 'graphic', 'explicit', 'nsfw'];
        $content = strtolower($item['title'] . ' ' . $item['description']);
        
        foreach ($sensitiveWords as $word) {
            if (strpos($content, $word) !== false) {
                return true;
            }
        }
        
        return false;
    }

    private function detectLanguage(string $text): string
    {
        // Simple language detection (you might want to use a proper library)
        $commonWords = [
            'en' => ['the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for'],
            'es' => ['el', 'la', 'y', 'o', 'pero', 'en', 'por', 'para', 'con'],
            'fr' => ['le', 'la', 'et', 'ou', 'mais', 'dans', 'sur', 'pour', 'avec'],
            'de' => ['der', 'die', 'das', 'und', 'oder', 'aber', 'in', 'auf', 'für']
        ];

        $words = str_word_count(strtolower($text), 1);
        $scores = [];

        foreach ($commonWords as $lang => $langWords) {
            $score = 0;
            foreach ($words as $word) {
                if (in_array($word, $langWords)) {
                    $score++;
                }
            }
            $scores[$lang] = $score;
        }

        return array_keys($scores, max($scores))[0] ?? 'en';
    }
}

// Usage example
$filters = [
    [
        'type' => 'date_range',
        'start' => '-30 days',
        'end' => 'now'
    ],
    [
        'type' => 'category_exclude',
        'categories' => ['spam', 'draft']
    ],
    [
        'type' => 'popularity',
        'min_score' => 50
    ]
];

$contentRules = [
    [
        'type' => 'truncate_description',
        'max_length' => 300
    ],
    [
        'type' => 'add_prefix',
        'condition' => ['field' => 'category', 'value' => 'urgent'],
        'prefix' => '[URGENT] '
    ],
    [
        'type' => 'modify_link',
        'utm_params' => [
            'utm_source' => 'rss',
            'utm_medium' => 'feed',
            'utm_campaign' => 'main_feed'
        ]
    ]
];

$generator = new ConditionalFeedGenerator($filters, $contentRules);
$items = fetchItemsFromDatabase(); // Your data source
echo $generator->generateFeed($items);

Dynamic Feed Discovery

Implement automatic feed discovery and management:

<?php

class FeedDiscoveryService
{
    private array $discoveredFeeds = [];
    private string $cacheDir;

    public function __construct(string $cacheDir = './feed_cache')
    {
        $this->cacheDir = $cacheDir;
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
    }

    public function discoverFeeds(string $url): array
    {
        $cacheKey = md5($url);
        $cacheFile = $this->cacheDir . '/discovery_' . $cacheKey . '.json';

        // Check cache
        if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 3600) {
            return json_decode(file_get_contents($cacheFile), true);
        }

        $feeds = [];
        
        try {
            // Get the HTML content
            $html = file_get_contents($url, false, stream_context_create([
                'http' => [
                    'timeout' => 10,
                    'user_agent' => 'Feed Discovery Bot 1.0'
                ]
            ]));

            if ($html) {
                $feeds = array_merge(
                    $this->findFeedsInHtml($html, $url),
                    $this->findCommonFeedUrls($url)
                );
            }
        } catch (Exception $e) {
            error_log("Feed discovery failed for {$url}: " . $e->getMessage());
        }

        // Cache results
        file_put_contents($cacheFile, json_encode($feeds));

        return $feeds;
    }

    private function findFeedsInHtml(string $html, string $baseUrl): array
    {
        $feeds = [];
        $dom = new DOMDocument();
        @$dom->loadHTML($html);
        $xpath = new DOMXPath($dom);

        // Look for link elements with rel="alternate"
        $links = $xpath->query('//link[@rel="alternate"][@type="application/rss+xml" or @type="application/atom+xml"]');
        
        foreach ($links as $link) {
            $href = $link->getAttribute('href');
            $title = $link->getAttribute('title') ?: 'RSS Feed';
            $type = $link->getAttribute('type');

            $feedUrl = $this->resolveUrl($href, $baseUrl);
            
            if ($this->validateFeed($feedUrl)) {
                $feeds[] = [
                    'url' => $feedUrl,
                    'title' => $title,
                    'type' => $type === 'application/atom+xml' ? 'atom' : 'rss',
                    'discovered_method' => 'html_link'
                ];
            }
        }

        return $feeds;
    }

    private function findCommonFeedUrls(string $baseUrl): array
    {
        $commonPaths = [
            '/feed',
            '/rss',
            '/rss.xml',
            '/feed.xml',
            '/atom.xml',
            '/index.xml',
            '/feeds/all.atom.xml',
            '/blog/feed',
            '/news/feed'
        ];

        $feeds = [];
        $parsedUrl = parse_url($baseUrl);
        $baseHost = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];

        foreach ($commonPaths as $path) {
            $feedUrl = $baseHost . $path;
            
            if ($this->validateFeed($feedUrl)) {
                $feeds[] = [
                    'url' => $feedUrl,
                    'title' => 'Auto-discovered Feed',
                    'type' => 'rss',
                    'discovered_method' => 'common_path'
                ];
            }
        }

        return $feeds;
    }

    private function validateFeed(string $url): bool
    {
        try {
            $context = stream_context_create([
                'http' => [
                    'method' => 'HEAD',
                    'timeout' => 5
                ]
            ]);

            $headers = get_headers($url, 1, $context);
            
            if (!$headers || strpos($headers[0], '200') === false) {
                return false;
            }

            $contentType = $headers['Content-Type'] ?? '';
            if (is_array($contentType)) {
                $contentType = $contentType[0];
            }

            return strpos($contentType, 'xml') !== false || 
                   strpos($contentType, 'rss') !== false ||
                   strpos($contentType, 'atom') !== false;

        } catch (Exception $e) {
            return false;
        }
    }

    private function resolveUrl(string $url, string $baseUrl): string
    {
        if (filter_var($url, FILTER_VALIDATE_URL)) {
            return $url;
        }

        $parsedBase = parse_url($baseUrl);
        $baseHost = $parsedBase['scheme'] . '://' . $parsedBase['host'];

        if (strpos($url, '/') === 0) {
            return $baseHost . $url;
        }

        return $baseHost . '/' . ltrim($url, '/');
    }

    public function createAutoDiscoveryHtml(array $feeds): string
    {
        $html = '';
        foreach ($feeds as $feed) {
            $type = $feed['type'] === 'atom' ? 'application/atom+xml' : 'application/rss+xml';
            $html .= sprintf(
                '<link rel="alternate" type="%s" title="%s" href="%s">' . "\n",
                htmlspecialchars($type),
                htmlspecialchars($feed['title']),
                htmlspecialchars($feed['url'])
            );
        }
        return $html;
    }
}

// Usage
$discovery = new FeedDiscoveryService();

// Discover feeds from a website
$feeds = $discovery->discoverFeeds('https://example.com');

// Generate auto-discovery HTML for your own site
$myFeeds = [
    ['url' => 'https://mysite.com/feed.xml', 'title' => 'Main Blog Feed', 'type' => 'rss'],
    ['url' => 'https://mysite.com/news.atom', 'title' => 'News Feed', 'type' => 'atom']
];

$autoDiscoveryHtml = $discovery->createAutoDiscoveryHtml($myFeeds);
echo $autoDiscoveryHtml; // Include this in your HTML <head>

Feed Analytics and Monitoring

Track feed performance and usage:

<?php

class FeedAnalytics
{
    private PDO $pdo;
    private string $logFile;

    public function __construct(PDO $pdo, string $logFile = './feed_analytics.log')
    {
        $this->pdo = $pdo;
        $this->logFile = $logFile;
        $this->initializeDatabase();
    }

    private function initializeDatabase(): void
    {
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS feed_analytics (
                id INT AUTO_INCREMENT PRIMARY KEY,
                feed_type VARCHAR(50),
                user_agent TEXT,
                ip_address VARCHAR(45),
                referer TEXT,
                requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                response_time_ms INT,
                cache_hit BOOLEAN DEFAULT FALSE,
                items_count INT,
                format VARCHAR(10)
            )
        ");
    }

    public function trackFeedRequest(array $data): void
    {
        // Log to database
        $stmt = $this->pdo->prepare("
            INSERT INTO feed_analytics 
            (feed_type, user_agent, ip_address, referer, response_time_ms, cache_hit, items_count, format)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ");
        
        $stmt->execute([
            $data['feed_type'],
            $data['user_agent'] ?? '',
            $data['ip_address'] ?? '',
            $data['referer'] ?? '',
            $data['response_time_ms'] ?? 0,
            $data['cache_hit'] ?? false,
            $data['items_count'] ?? 0,
            $data['format'] ?? 'rss'
        ]);

        // Log to file
        $logEntry = [
            'timestamp' => date('c'),
            'feed_type' => $data['feed_type'],
            'ip' => $data['ip_address'] ?? 'unknown',
            'user_agent' => $data['user_agent'] ?? 'unknown',
            'response_time' => $data['response_time_ms'] ?? 0,
            'cache_hit' => $data['cache_hit'] ?? false
        ];

        file_put_contents($this->logFile, json_encode($logEntry) . "\n", FILE_APPEND | LOCK_EX);
    }

    public function getAnalyticsSummary(int $days = 30): array
    {
        $stmt = $this->pdo->prepare("
            SELECT 
                feed_type,
                format,
                COUNT(*) as requests,
                AVG(response_time_ms) as avg_response_time,
                SUM(cache_hit) as cache_hits,
                AVG(items_count) as avg_items,
                COUNT(DISTINCT ip_address) as unique_visitors
            FROM feed_analytics 
            WHERE requested_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY feed_type, format
            ORDER BY requests DESC
        ");

        $stmt->execute([$days]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function getPopularUserAgents(int $limit = 10): array
    {
        $stmt = $this->pdo->prepare("
            SELECT 
                user_agent,
                COUNT(*) as requests,
                AVG(response_time_ms) as avg_response_time
            FROM feed_analytics 
            WHERE requested_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
            AND user_agent != ''
            GROUP BY user_agent
            ORDER BY requests DESC
            LIMIT ?
        ");

        $stmt->execute([$limit]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function detectFeedReaders(): array
    {
        $stmt = $this->pdo->query("
            SELECT 
                CASE 
                    WHEN user_agent LIKE '%Feedly%' THEN 'Feedly'
                    WHEN user_agent LIKE '%NewsBlur%' THEN 'NewsBlur'
                    WHEN user_agent LIKE '%Inoreader%' THEN 'Inoreader'
                    WHEN user_agent LIKE '%FeedBurner%' THEN 'FeedBurner'
                    WHEN user_agent LIKE '%curl%' THEN 'cURL/Script'
                    WHEN user_agent LIKE '%bot%' OR user_agent LIKE '%spider%' THEN 'Bot/Crawler'
                    ELSE 'Other/Browser'
                END as client_type,
                COUNT(*) as requests
            FROM feed_analytics 
            WHERE requested_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
            GROUP BY client_type
            ORDER BY requests DESC
        ");

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function getPerformanceMetrics(): array
    {
        $stmt = $this->pdo->query("
            SELECT 
                DATE(requested_at) as date,
                COUNT(*) as total_requests,
                AVG(response_time_ms) as avg_response_time,
                MIN(response_time_ms) as min_response_time,
                MAX(response_time_ms) as max_response_time,
                SUM(cache_hit) / COUNT(*) * 100 as cache_hit_rate
            FROM feed_analytics 
            WHERE requested_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
            GROUP BY DATE(requested_at)
            ORDER BY date DESC
        ");

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

// Analytics-enabled feed generator
class AnalyticsEnabledFeed
{
    private FeedAnalytics $analytics;

    public function __construct(FeedAnalytics $analytics)
    {
        $this->analytics = $analytics;
    }

    public function generateFeed(string $feedType, string $format = 'rss'): string
    {
        $startTime = microtime(true);
        $cacheHit = false;

        // Your feed generation logic here
        $feed = FeedFactory::create();
        // ... configure and populate feed ...

        $items = $this->getItems($feedType);
        foreach ($items as $item) {
            $feed->addItem($item);
        }

        $output = $feed->render($format);
        
        $responseTime = (microtime(true) - $startTime) * 1000;

        // Track analytics
        $this->analytics->trackFeedRequest([
            'feed_type' => $feedType,
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'ip_address' => $this->getRealIpAddress(),
            'referer' => $_SERVER['HTTP_REFERER'] ?? '',
            'response_time_ms' => round($responseTime),
            'cache_hit' => $cacheHit,
            'items_count' => count($items),
            'format' => $format
        ]);

        return $output;
    }

    private function getRealIpAddress(): string
    {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            return $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        }
    }

    private function getItems(string $feedType): array
    {
        // Your data fetching logic
        return [
            [
                'title' => 'Sample Item',
                'description' => 'Sample description',
                'link' => 'https://example.com/item',
                'pubdate' => date('c')
            ]
        ];
    }
}

Advanced Security Features

Implement security measures for feeds:

<?php

class SecureFeedGenerator
{
    private array $allowedIps;
    private array $blockedUserAgents;
    private int $rateLimitPerHour;
    private string $apiKey;

    public function __construct(array $config = [])
    {
        $this->allowedIps = $config['allowed_ips'] ?? [];
        $this->blockedUserAgents = $config['blocked_user_agents'] ?? [];
        $this->rateLimitPerHour = $config['rate_limit_per_hour'] ?? 100;
        $this->apiKey = $config['api_key'] ?? '';
    }

    public function generateSecureFeed(string $feedType): array
    {
        // Security checks
        if (!$this->validateAccess()) {
            return [
                'status' => 403,
                'body' => 'Access denied',
                'headers' => []
            ];
        }

        if (!$this->checkRateLimit()) {
            return [
                'status' => 429,
                'body' => 'Rate limit exceeded',
                'headers' => ['Retry-After' => '3600']
            ];
        }

        // Generate feed
        $feed = FeedFactory::create();
        // ... configure feed ...

        $output = $feed->render('rss');

        // Add security headers
        $headers = [
            'Content-Type' => 'application/rss+xml; charset=utf-8',
            'X-Content-Type-Options' => 'nosniff',
            'X-Frame-Options' => 'DENY',
            'Cache-Control' => 'public, max-age=3600',
            'ETag' => md5($output),
        ];

        return [
            'status' => 200,
            'body' => $output,
            'headers' => $headers
        ];
    }

    private function validateAccess(): bool
    {
        // IP whitelist check
        if (!empty($this->allowedIps)) {
            $clientIp = $this->getRealIpAddress();
            if (!in_array($clientIp, $this->allowedIps)) {
                return false;
            }
        }

        // User agent blocking
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        foreach ($this->blockedUserAgents as $blockedAgent) {
            if (stripos($userAgent, $blockedAgent) !== false) {
                return false;
            }
        }

        // API key validation (if required)
        if ($this->apiKey) {
            $providedKey = $_GET['api_key'] ?? $_SERVER['HTTP_X_API_KEY'] ?? '';
            if (!hash_equals($this->apiKey, $providedKey)) {
                return false;
            }
        }

        return true;
    }

    private function checkRateLimit(): bool
    {
        $clientIp = $this->getRealIpAddress();
        $cacheKey = 'rate_limit_' . md5($clientIp);
        $cacheFile = sys_get_temp_dir() . '/' . $cacheKey;

        $currentHour = date('Y-m-d-H');
        $data = [];

        if (file_exists($cacheFile)) {
            $data = json_decode(file_get_contents($cacheFile), true) ?: [];
        }

        // Clean old data
        $data = array_filter($data, function ($hour) use ($currentHour) {
            return $hour === $currentHour;
        }, ARRAY_FILTER_USE_KEY);

        $currentCount = $data[$currentHour] ?? 0;

        if ($currentCount >= $this->rateLimitPerHour) {
            return false;
        }

        // Update counter
        $data[$currentHour] = $currentCount + 1;
        file_put_contents($cacheFile, json_encode($data));

        return true;
    }

    private function getRealIpAddress(): string
    {
        $ipHeaders = [
            'HTTP_CF_CONNECTING_IP',     // Cloudflare
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        ];

        foreach ($ipHeaders as $header) {
            if (!empty($_SERVER[$header])) {
                $ips = explode(',', $_SERVER[$header]);
                $ip = trim($ips[0]);
                
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }

    public function sanitizeContent(string $content): string
    {
        // Remove potentially dangerous content
        $content = strip_tags($content, '<p><br><strong><em><a><ul><ol><li><h1><h2><h3><h4><h5><h6>');
        
        // Sanitize URLs in links
        $content = preg_replace_callback('/<a\s+href="([^"]*)"/', function ($matches) {
            $url = filter_var($matches[1], FILTER_SANITIZE_URL);
            return '<a href="' . htmlspecialchars($url) . '"';
        }, $content);

        return $content;
    }
}

Webhook Integration

Implement webhook notifications for feed updates:

<?php

class FeedWebhookManager
{
    private array $webhooks;
    private int $timeout;

    public function __construct(array $webhooks = [], int $timeout = 10)
    {
        $this->webhooks = $webhooks;
        $this->timeout = $timeout;
    }

    public function notifySubscribers(string $feedType, array $newItems): void
    {
        $payload = [
            'timestamp' => date('c'),
            'feed_type' => $feedType,
            'new_items_count' => count($newItems),
            'items' => array_slice($newItems, 0, 10), // Send max 10 items
            'feed_url' => $this->getFeedUrl($feedType)
        ];

        foreach ($this->webhooks as $webhook) {
            if ($this->shouldNotify($webhook, $feedType)) {
                $this->sendWebhook($webhook, $payload);
            }
        }
    }

    private function shouldNotify(array $webhook, string $feedType): bool
    {
        if (isset($webhook['feed_types'])) {
            return in_array($feedType, $webhook['feed_types']);
        }
        return true; // Notify for all feeds if no filter specified
    }

    private function sendWebhook(array $webhook, array $payload): void
    {
        $headers = [
            'Content-Type: application/json',
            'User-Agent: Feed Webhook Bot 1.0'
        ];

        if (isset($webhook['secret'])) {
            $signature = hash_hmac('sha256', json_encode($payload), $webhook['secret']);
            $headers[] = 'X-Hub-Signature-256: sha256=' . $signature;
        }

        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => implode("\r\n", $headers),
                'content' => json_encode($payload),
                'timeout' => $this->timeout
            ]
        ]);

        $response = @file_get_contents($webhook['url'], false, $context);
        
        // Log webhook delivery status
        $this->logWebhookDelivery($webhook['url'], $response !== false);
    }

    private function logWebhookDelivery(string $url, bool $success): void
    {
        $logEntry = [
            'timestamp' => date('c'),
            'webhook_url' => $url,
            'success' => $success,
            'delivery_id' => uniqid()
        ];

        file_put_contents('./webhook_log.json', json_encode($logEntry) . "\n", FILE_APPEND | LOCK_EX);
    }

    private function getFeedUrl(string $feedType): string
    {
        return "https://example.com/feed/{$feedType}";
    }
}

// Usage in your feed update process
$webhookManager = new FeedWebhookManager([
    [
        'url' => 'https://subscriber1.com/webhook',
        'secret' => 'webhook_secret_key',
        'feed_types' => ['blog', 'news']
    ],
    [
        'url' => 'https://subscriber2.com/feed-updates',
        'feed_types' => ['all']
    ]
]);

// When new content is published
$newItems = [
    [
        'title' => 'New Blog Post',
        'link' => 'https://example.com/new-post',
        'pubdate' => date('c')
    ]
];

$webhookManager->notifySubscribers('blog', $newItems);

Performance Optimization

Advanced performance optimization techniques:

<?php

class OptimizedFeedGenerator
{
    private $cache;
    private $database;
    private array $config;

    public function __construct($cache, $database, array $config = [])
    {
        $this->cache = $cache;
        $this->database = $database;
        $this->config = array_merge([
            'enable_compression' => true,
            'enable_etag' => true,
            'enable_streaming' => false,
            'max_items' => 50,
            'cache_ttl' => 3600
        ], $config);
    }

    public function generateOptimizedFeed(string $feedType): void
    {
        // Handle compression
        if ($this->config['enable_compression'] && $this->acceptsCompression()) {
            ob_start('ob_gzhandler');
        }

        // ETag support
        $etag = $this->generateETag($feedType);
        if ($this->config['enable_etag'] && $this->clientHasETag($etag)) {
            http_response_code(304);
            header("ETag: {$etag}");
            return;
        }

        // Generate or get cached feed
        $cacheKey = "feed:{$feedType}:optimized";
        $feedXml = $this->cache->get($cacheKey);

        if ($feedXml === null) {
            $feedXml = $this->generateFeedContent($feedType);
            $this->cache->set($cacheKey, $feedXml, $this->config['cache_ttl']);
        }

        // Set headers
        $this->setOptimalHeaders($etag, strlen($feedXml));

        // Stream large feeds
        if ($this->config['enable_streaming'] && strlen($feedXml) > 1024 * 1024) {
            $this->streamOutput($feedXml);
        } else {
            echo $feedXml;
        }
    }

    private function generateFeedContent(string $feedType): string
    {
        $feed = FeedFactory::create();
        $feed->setTitle("Optimized {$feedType} Feed");
        $feed->setDescription("High-performance feed");
        $feed->setLink('https://example.com');

        // Optimized database query
        $items = $this->fetchOptimizedItems($feedType);

        foreach ($items as $item) {
            $feed->addItem($item);
        }

        return $feed->render('rss');
    }

    private function fetchOptimizedItems(string $feedType): array
    {
        // Use optimized query with proper indexing
        $sql = "
            SELECT 
                p.title,
                p.slug,
                p.excerpt,
                p.created_at,
                u.name as author_name,
                CONCAT('https://example.com/posts/', p.slug) as url
            FROM posts p
            INNER JOIN users u ON p.author_id = u.id
            WHERE p.published = 1 
            AND p.type = ?
            ORDER BY p.created_at DESC
            LIMIT ?
        ";

        $stmt = $this->database->prepare($sql);
        $stmt->execute([$feedType, $this->config['max_items']]);

        $items = [];
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $items[] = [
                'title' => $row['title'],
                'description' => $row['excerpt'],
                'link' => $row['url'],
                'pubdate' => $row['created_at'],
                'author' => $row['author_name']
            ];
        }

        return $items;
    }

    private function generateETag(string $feedType): string
    {
        // Generate ETag based on last modification time
        $lastModified = $this->getLastModificationTime($feedType);
        return '"' . md5($feedType . $lastModified) . '"';
    }

    private function getLastModificationTime(string $feedType): int
    {
        $stmt = $this->database->prepare("
            SELECT UNIX_TIMESTAMP(MAX(updated_at)) as last_modified
            FROM posts 
            WHERE published = 1 AND type = ?
        ");
        $stmt->execute([$feedType]);
        
        return $stmt->fetchColumn() ?: time();
    }

    private function clientHasETag(string $etag): bool
    {
        $clientETag = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
        return $clientETag === $etag;
    }

    private function acceptsCompression(): bool
    {
        $acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
        return strpos($acceptEncoding, 'gzip') !== false;
    }

    private function setOptimalHeaders(string $etag, int $contentLength): void
    {
        header('Content-Type: application/rss+xml; charset=utf-8');
        header("ETag: {$etag}");
        header('Cache-Control: public, max-age=' . $this->config['cache_ttl']);
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $this->getLastModificationTime('blog')) . ' GMT');
        header("Content-Length: {$contentLength}");
        header('Vary: Accept-Encoding');
    }

    private function streamOutput(string $content): void
    {
        $chunkSize = 8192;
        $contentLength = strlen($content);
        
        for ($i = 0; $i < $contentLength; $i += $chunkSize) {
            echo substr($content, $i, $chunkSize);
            flush();
        }
    }
}

These advanced features provide enterprise-level capabilities for complex feed scenarios, including content aggregation, conditional filtering, automatic discovery, analytics tracking, security measures, webhook notifications, and performance optimization.

Next Steps

Clone this wiki locally