-
Notifications
You must be signed in to change notification settings - Fork 94
Advanced Features
Rumen Damyanov edited this page Jul 29, 2025
·
1 revision
This guide covers advanced features and patterns for complex feed scenarios, performance optimization, and enterprise-level implementations.
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();
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);
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>
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')
]
];
}
}
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;
}
}
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);
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.
- Review Caching Examples for performance optimization
- Check Custom Views for advanced templating
- Explore framework-specific implementations: Laravel or Symfony