danekfeed

Fork feed-io for (PHP >= 7.1 && PHP < 8) - 用于消费和提供JSONFeed / RSS / Atom订阅源的PHP库

dev-main 2022-08-20 21:56 UTC

This package is auto-updated.

Last update: 2024-09-21 02:28:16 UTC


README

通知

这是一个4分支的分支版本,由于需要运行最低PHP 7.1+包括后续版本,因此存在。原始库处理后续版本的PHP环境。分支的内容已被清除。它已被移除了我不使用的测试和其他工具。

这是一个用于消费和提供新闻订阅源的PHP库。它具有以下特点:

  • 支持JSONFeed / Atom / RSS的读写
  • 通过HTML头部自动发现订阅源
  • 通过异步请求一次性读取多个订阅源
  • 使用准确的缓存头部生成PSR-7响应
  • 在读取订阅源时支持HTTP头部以节省网络流量
  • 在读取订阅源时检测格式(RSS / Atom)
  • 支持封装以处理外部媒体,如音频内容
  • 支持订阅源Logo(RSS + Atom)
  • PSR兼容的日志记录
  • 内容过滤以获取最新条目
  • 自动纠正损坏的订阅源
  • 检测和转换DateTime
  • 通用的HTTP ClientInterface
  • Guzzle客户端集成

安装

使用Composer将feed-io添加到项目的需求中

    composer require danek/feed-io

要求

feed-io需要

  • php 7.1+
  • psr/log 1.0
  • guzzlehttp/guzzle 6.2+

Monolog不是处理feed-io日志的唯一库,您可以使用任何PSR/Log兼容库。

获取仓库

如果您想做出贡献(并欢迎您这样做)

    git clone https://github.com/jDanek/feed-io.git

    cd feed-io/

    composer install

用法

读取

feed-io旨在读取互联网上的订阅源并发布您自己的订阅源。其主要类是FeedIo

// create a simple FeedIo instance
$feedIo = \Danek\FeedIo\Factory::create()->getFeedIo();

// read a feed
$result = $feedIo->read($url);

// or read a feed since a certain date
$result = $feedIo->readSince($url, new \DateTime('-7 days'));

// get title
$feedTitle = $result->getFeed()->getTitle();

// iterate through items
foreach( $result->getFeed() as $item ) {
    echo $item->getTitle();
}

为了节省带宽,feed-io会估计下一次读取订阅源并从中获取新条目的相关时间。

$nextUpdate = $result->getNextUpdate();
echo "computed next update: {$nextUpdate->format(\DATE_ATOM)}";

// you may need to access the statistics
$updateStats = $result->getUpdateStats();
echo "average interval in seconds: {$updateStats->getAverageInterval()}";

feed-io通过首先检测订阅源在过去7天内是否活跃,如果不是,则将其视为休眠。休眠订阅源的下一次更新日期设置为第二天同一时间。如果订阅源不处于休眠状态,则使用平均间隔和中间间隔,将间隔加到订阅源的最后修改日期上,并将结果与当前时间进行比较。如果结果是未来的,则返回为下一次更新时间。如果没有一个是未来的,则认为订阅源将很快更新,因此下一次更新时间是计算时刻后一小时。

请注意:休眠和即将更新的订阅源的固定延迟可以通过Result::getNextUpdate()参数设置,有关更多详细信息,请参阅Result

一次性异步读取多个订阅源

多亏了Guzzle,feed-io能够通过异步请求一次性获取多个订阅源。如果您想了解更多关于其工作方式的信息,可以阅读Guzzle的文档

要使用feed-io异步请求读取源,您需要向\Danek\FeedIo\FeedIo::readAsync发送一组\Danek\FeedIo\Async\Request对象,并使用您自己的\Danek\FeedIo\Async\CallbackInterface处理结果。您还可以使用\Danek\FeedIo\Async\DefaultCallback来测试功能。

每个\Danek\FeedIo\Async\Request都是一个您想要执行请求,它包含源URL和可选的\DateTime来定义请求的modified-since属性。

CallbackInterface实例需要两个方法

  /**
   * @param Result $result
   */
  public function process(Result $result) : void;

  /**
   * @param Request $request
   * @param \Exception $exception
   */
  public function handleError(Request $request, \Exception $exception) : void;

process()在成功读取和解析后被调用,以便您处理结果。否则,在调用错误时将触发handleError()。以下是一个示例:PDOCallback

源发现

一个网页可以在其头部引用一个或多个源,feed-io提供了一种发现它们的方法

$feedIo = \Danek\FeedIo\Factory::create()->getFeedIo();

$feeds = $feedIo->discover($url);

foreach( $feeds as $feed ) {
    echo "discovered feed : {$feed}";
}

或者您可以使用feed-io的命令行

./vendor/bin/feedio discover https://a-website.org

您将在输出中获得所有发现的源。

将对象格式化为XML流

// build the feed
$feed = new Danek\Danek\FeedIo\Feed;
$feed->setTitle('...');

// convert it into Atom
$atomString = $feedIo->toAtom($feed);

// or ...
$atomString = $feedIo->format($feed, 'atom');

添加样式表

$feed = new \Danek\FeedIo\FeedIo\Feed;
$feed->setTitle('...');
$styleSheet = new StyleSheet('http://url-of-the-xsl-stylesheet.xsl');
$feed->setStyleSheet($styleSheet);

构建包含媒体的源

// build the feed
$feed = new \Danek\FeedIo\FeedIo\Feed;
$feed->setTitle('...');

$item = $feed->newItem();

// add namespaces
$feed->setNS(
    'itunes', //namespace
    'http://www.itunes.com/dtds/podcast-1.0.dtd' //dtd for the namespace
        );
$feed->set('itunes,title', 'Sample Title'); //OR any other element defined in the namespace.
$item->addElement('itunes:category', 'Education');

// build the media
$media = new \Danek\FeedIo\Feed\Item\Media
$media->setUrl('http://yourdomain.tld/medias/some-podcast.mp3');
$media->setType('audio/mpeg');

// add it to the item
$item->addMedia($media);

$feed->add($item);

使用源创建有效的PSR-7响应

您可以使用\Danek\FeedIo\FeedIo::getPsrResponse()\Danek\FeedIo\FeedInstance直接转换为PSR-7有效响应。

$feed = new \Danek\FeedIo\Feed;

// feed the beast ...
$item = new \Danek\FeedIo\Feed\Item;
$item->set ...
$feed->add($item);

$atomResponse = $feedIo->getPsrResponse($feed, 'atom');

$jsonResponse = $feedIo->getPsrResponse($feed, 'json');

使用工厂配置feed-io

读取部分中,我们看到可以通过静态调用Factory来获取简单的FeedIo实例,并让它返回一个由主要依赖项组成的新的FeedIo。问题是我们可能想要向其底层组件注入配置,例如配置Guzzle忽略SSL错误。

为此,我们将通过Factory::create()参数注入配置,第一个是用于日志系统,第二个是用于HTTP客户端(好吧,Guzzle)。

通过工厂配置Guzzle

在上面的几行中,我们谈到了忽略SSL错误,让我们看看如何配置Guzzle来完成这项工作

$feedIo = \Danek\FeedIo\Factory::create(
        ['builder' => 'NullLogger'], // assuming you want feed-io to keep quiet
        ['builder' => 'GuzzleClient', 'config' => ['verify' => false]]
    )->getFeedIo();

指定“builder”非常重要,因为这个类将负责实际构建实例。

激活日志

feed-io原生支持PSR-3日志,您可以通过在工厂中选择一个'builder'来激活它

$feedIo = \Danek\FeedIo\Factory::create(['builder' => 'monolog'])->getFeedIo();

feed-io仅提供创建Monolog\Logger实例的builder。您可以编写自己的,只要Builder实现BuilderInterface即可。

不使用工厂构建FeedIo实例

要创建一个新的FeedIo实例,您只需要注入两个依赖项

  • 一个实现FeedIo\Adapter\ClientInterface的HTTP客户端。它可以是对外部库(如FeedIo\Adapter\Guzzle\Client)的包装
  • 一个实现Psr\Log\LoggerInterface的PSR-3日志记录器
// first dependency : the HTTP client
// here we use Guzzle as a dependency for the client
$guzzle = new GuzzleHttp\Client();
// Guzzle is wrapped in this adapter which is a FeedIo\Adapter\ClientInterface  implementation
$client = new \Danek\FeedIo\FeedIo\Adapter\Guzzle\Client($guzzle);

// second dependency : a PSR-3 logger
$logger = new Psr\Log\NullLogger();

// now create FeedIo's instance
$feedIo = new \Danek\FeedIo\FeedIo\FeedIo($client, $logger);

另一个使用配置为将输出写入标准输出的Monolog的示例

use \Danek\FeedIo\FeedIo\FeedIo;
use \Danek\FeedIo\FeedIo\Adapter\Guzzle\Client;
use GuzzleHttp\Client as GuzzleClient;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$client = new Client(new GuzzleClient());
$logger = new Logger('default', [new StreamHandler('php://stdout')]);

$feedIo = new FeedIo($client, $logger);

Guzzle配置

您可以在将其注入到FeedIo之前配置Guzzle

use \Danek\FeedIo\FeedIo\FeedIo;
use \Danek\FeedIo\FeedIo\Adapter\Guzzle\Client;
use GuzzleHttp\Client as GuzzleClient;
use \Psr\Log\NullLogger;

// We want to timeout after 3 seconds
$guzzle = new GuzzleClient(['timeout' => 3]);
$client = new Client($guzzle);

$logger = new NullLogger();

$feedIo = new \Danek\FeedIo\FeedIo($client, $logger);

请阅读Guzzle的文档以获取有关其配置的更多信息。

缓存中间件的使用

为了防止您的应用程序多次击中相同的源,您可以将Kevin Rob的缓存中间件注入到Guzzle的实例中

use \Danek\FeedIo\FeedIo\FeedIo;
use \Danek\FeedIo\FeedIo\Adapter\Guzzle\Client;
use GuzzleHttp\Client As GuzzleClient;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Psr\Log\NullLogger;

// Create default HandlerStack
$stack = HandlerStack::create();

// Add this middleware to the top with `push`
$stack->push(new CacheMiddleware(), 'cache');

// Initialize the client with the handler option
$guzzle = new GuzzleClient(['handler' => $stack]);
$client = new Client($guzzle);
$logger = new NullLogger();

$feedIo = new \Danek\FeedIo\FeedIo($client, $logger);

由于源的内容可能经常变化,缓存可能导致不期望的行为。

注入自定义Logger

只要它实现了Psr\Log\LoggerInterface,您就可以注入任何Logger。Monolog做到了,但它是唯一的库:https://packagist.org.cn/providers/psr/log-implementation

use \Danek\FeedIo\FeedIo\FeedIo;
use \Danek\FeedIo\FeedIo\Adapter\Guzzle\Client;
use GuzzleHttp\Client as GuzzleClient;
use Custom\Logger;

$client = new Client(new GuzzleClient());
$logger = new Logger();

$feedIo = new FeedIo($client, $logger);

注入自定义HTTP客户端

警告:强烈建议使用默认的Guzzle客户端集成。

如果您真的想使用其他库来读取源,您需要创建自己的FeedIo\Adapter\ClientInterface类来嵌入与库的交互

use \Danek\FeedIo\FeedIo\FeedIo;
use Custom\Adapter\Client;
use Library\Client as LibraryClient;
use Psr\Log\NullLogger;

$client = new Client(new LibraryClient());
$logger = new NullLogger();

$feedIo = new FeedIo($client, $logger);

工厂模式或依赖注入?

在项目的某个阶段,您必须问自己是否要使用工厂模式或者不使用它来构建FeedIo。工厂模式主要是为了让您能够以更少的努力使用feed-io,并在很短的时间内获得初步结果。然而,它并不让您充分利用Monolog和Guzzle的所有功能,这可能会让人烦恼。依赖注入也允许您在需要时选择另一个库来处理日志。

处理缺失时区的问题

有时您必须处理缺少时区的日期的源。在某些情况下,您可能需要指定源的时区以获得准确的值,因此feed-io为此提供了一个解决方案

$feedIo->getDateTimeBuilder()->setFeedTimezone(new \DateTimeZone($feedTimezone));
$result = $feedIo->read($feedUrl);
$feedIo->getDateTimeBuilder()->resetFeedTimezone();

别忘了在获取结果后重置feedTimezone,否则所有源都会位于同一时区。