caseyamcl/phpoaipmh

A PHP OAI-PMH 2.0 收割库

v3.3.1 2023-01-27 17:40 UTC

This package is auto-updated.

Last update: 2024-08-24 17:17:01 UTC


README

A PHP OAI-PMH 收割客户端库

Latest Version Total Downloads Software License Github Build Code coverage Scrutinizer

此库提供了一个接口,可以从任何符合 OAI 2.0 规范的端点收割 OAI-PMH 元数据。

特性

  • PSR-12 符合
  • Composer 兼容
  • 单元测试
  • 首选 Guzzle (v6, v7 或 v5) 作为 HTTP 传输层,但也可以回退到 cURL,或者实现自己的
  • 易于使用的迭代器,隐藏获取分页记录所需的全部 HTTP 杂项

安装选项

通过将以下内容包含在您的 composer.json 文件中通过 Composer 安装

{
    "require": {
        "caseyamcl/phpoaipmh": "^3.0",
        "guzzlehttp/guzzle":   "^7.0"
    }
}

或者,将 src 文件夹放入您的应用程序中,并使用 PSR-4 自动加载器包含文件。

注意:推荐使用 Guzzle v6.0 或 v7.0,但如果不希望使用 Guzzle v6,出于任何原因,可以使用以下之一

  • Guzzle 5.0 - 您可以使用 Guzzle v5 替代 v6。
  • cURL - 如果未安装 Guzzle,此库将回退到使用 cURL。
  • 自行构建 - 您可以通过将您自己的 Phpoaipmh\HttpAdapter\HttpAdapterInterface 实现传递给 Phpoaipmh\Client 构造函数来使用不同的 HTTP 客户端库。

升级

在主要版本变更中,有几个向后不兼容的 API 改进。有关如何将您的代码升级到新版本的信息,请参阅 <UPGRADE.md>。

使用方法

设置新的端点客户端

// Quick and easy 'build' method 
$myEndpoint = \Phpoaipmh\Endpoint::build('http://some.service.com/oai');

// Or, create your own client instance and pass it to `Endpoint::__construct()` 
$client = new \Phpoaipmh\Client('http://some.service.com/oai');
$myEndpoint = new \Phpoaipmh\Endpoint($client);

获取基本信息

// Result will be a SimpleXMLElement object
$result = $myEndpoint->identify();
var_dump($result);

// Results will be iterator of SimpleXMLElement objects
$results = $myEndpoint->listMetadataFormats();
foreach($results as $item) {
    var_dump($item);
}

检索记录

// Recs will be an iterator of SimpleXMLElement objects
$recs = $myEndpoint->listRecords('someMetaDataFormat');

// The iterator will continue retrieving items across multiple HTTP requests.
// You can keep running this loop through the *entire* collection you
// are harvesting.  All OAI-PMH and HTTP pagination logic is hidden neatly
// behind the iterator API.
foreach($recs as $rec) {
    var_dump($rec);
}

通过日期/时间限制记录检索

只需将 DateTimeInterface 实例传递给 Endpoint::listRecords()Endpoint::listIdentifiers() 作为参数二和三。

如果您只想获取其中一个而不是另一个,则可以传递 null 作为任一参数。

// Retrieve records from Jan 1, 2018 through October 1, 2018
$recs = $myEndpoint->listRecords('someMetaDataFormat', new \DateTime('2018-01-01'), new \DateTime('2018-10-01'));

foreach($recs as $rec) {
    var_dump($rec);
}

设置日期/时间粒度

此库将尝试从 OAI-PMH Identify 端点自动检索粒度,但如果您想手动设置它,则可以将 Granularity 实例传递给 Endpoint 构造函数。

use Phpoaipmh\Client,
    Phpoaipmh\Endpoint,
    Phpoaipmh\Granularity;

$client = new Client('http://some.service.com/oai');
$myEndpoint = new Endpoint($client, Granularity::DATE_AND_TIME);

记录集

一些 OAI-PMH 端点将记录细分到 中。

您可以通过调用 Endpoint::listSets() 来列出给定端点可用的记录集。

foreach ($myEndpoint->listSets() as $set) {
    var_dump($set);
}

您可以通过将集名称作为第四个参数传递给 Endpoint::listIdentifiers()Endpoint::listRecords() 来指定您希望检索的集。

foreach ($myEndpoint->listRecords('someMetadataFormat', null, null 'someSetName') as $record) {
    var_dump($record);
}

获取总记录数

某些端点为您提供查询的总记录数。如果端点提供此信息,则可以通过调用 RecordIterator::getTotalRecordCount() 来访问此值。

如果端点不提供此计数,则 RecordIterator::getTotalRecordCount() 返回 null

$iterator = $myEndpoint->listRecords('someMetaDataFormat');
echo "Total count is " . ($iterator->getTotalRecordCount() ?: 'unknown');

处理结果

根据您使用的动词,库将返回一个 SimpleXMLELement 或一个包含 SimpleXMLElement 对象的迭代器。

  • 对于 identifygetRecord,返回一个 SimpleXMLElement 对象
  • 对于 listMetadataFormatslistSetslistIdentifierslistRecords,返回一个 Phpoaipmh\ResponseIterator

Phpoaipmh\ResponseIterator 对象封装了迭代分页记录集的逻辑。

处理错误

在此库的不同情况下将抛出不同的异常

  • HTTP 请求错误将生成一个 Phpoaipmh\Exception\HttpException
  • 响应体解析问题(例如无效的 XML)将生成一个 Phpoaipmh\Exception\MalformedResponseException
  • OAI-PMH 协议错误(例如无效的操作或缺少参数)将生成一个 Phpoaipmh\Exception\OaipmhException

所有异常都扩展了 Phpoaipmh\Exception\BaseoaipmhException 类。

自定义默认请求选项

您可以通过手动构建适配器对象来自定义 cURL 和 Guzzle 客户端的默认请求选项(例如,请求超时)。

如果您使用的是 Guzzle v6,您可以通过构建自己的 Guzzle 客户端并在构造函数中 设置参数 来设置默认选项。

use GuzzleHttp\Client as GuzzleClient;
use Phpoaipmh\Client;
use Phpoaipmh\Endpoint;
use Phpoaipmh\HttpAdapter\GuzzleAdapter;

$guzzle = new GuzzleAdapter(new GuzzleClient([
    'connect_timeout' => 2.0,
    'timeout'         => 10.0
]));

$myEndpoint = new Endpoint(new Client('http://some.service.com/oai', $guzzle));

如果您使用的是 cURL,您可以通过将它们作为键值项数组传递给 CurlAdapter::setCurlOpts() 来设置请求选项。

use Phpoaipmh\Client,
    Phpoaipmh\HttpAdapter\CurlAdapter;

$adapter = new CurlAdapter();
$adapter->setCurlOpts([CURLOPT_TIMEOUT => 120]);
$client = new Client('http://some.service.com/oai', $adapter);

$myEndpoint = new Endpoint($client);

如果您使用的是 Guzzle v5,您可以通过构建自己的 Guzzle 客户端来设置默认选项,

use Phpoaipmh\Client,
    Phpoaipmh\HttpAdapter\GuzzleAdapter;

$adapter = new GuzzleAdapter();
$adapter->getGuzzleClient()->setDefaultOption('timeout', 120);
$client = new Client('http://some.service.com/oai', $adapter);

$myEndpoint = new Endpoint($client);

处理 XML 命名空间

许多 OAI-PMH XML 文档使用 XML 命名空间。对于非 XML 专家来说,在 PHP 中实现这些可能很令人困惑。SitePoint 提供了一个简要但出色的 关于如何在 SimpleXML 中使用命名空间的概述

迭代器元数据

Phpoaipmh\RecordIterator 迭代器包含一些辅助方法

  • getNumRequests() - 返回到目前为止发出的 HTTP 请求数量
  • getNumRetrieved() - 返回检索到的单个记录数量
  • reset() - 重置迭代器,这将从头开始重新启动记录检索。

处理 503 Retry-After 响应

某些 OAI-PMH 端点使用速率限制,以便您在给定时间段内只能发出 X 个请求。如果您的代码生成太多太快,这些端点将返回 503 Retry-After HTTP 状态码。

Guzzle v6

如果您已安装 Guzzle v6,则可以使用 Guzzle-Retry-Middleware 库来自动处理 OAI-PMH 端点速率限制规则。

首先,将中间件作为依赖项包含在您的应用程序中

composer require caseyamcl/guzzle_retry_middleware

然后,在加载 Phpoaipmh 库时,手动构建 Guzzle 客户端,并将中间件添加到堆栈中。示例

use GuzzleRetry\GuzzleRetryMiddleware;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\HandlerStack;

// Setup the the Guzzle client with the retry middleware
$stack = HandlerStack::create();
$stack->push(GuzzleRetryMiddleware::factory());
$guzzleClient = new GuzzleClient(['handler' => $stack]);

// Setup the Guzzle adpater and PHP OAI-PMH client
$guzzleAdapter = new \Phpoaipmh\HttpAdapter\GuzzleAdapter($guzzleClient);
$client  = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter);

这将创建一个客户端,当 OAI-PMH 端点发送 503 速率限制响应时,会自动重试请求。

重试中间件包含许多选项。有关详细信息,请参阅该软件包的 README

Guzzle v5

如果您已安装 Guzzle v5,则可以使用 Retry-Subscriber 来自动处理 OAI-PMH 端点速率限制规则。

首先,在您的 composer.json 中包含重试订阅者作为依赖项

require: {
    /* ... */
   "guzzlehttp/retry-subscriber": "~2.0"
}

然后,在加载 Phpoaipmh 库时,手动实例化 Guzzle 适配器,并按以下代码所示添加订阅者

// Create a Retry Guzzle Subscriber
$retrySubscriber = new \GuzzleHttp\Subscriber\Retry\RetrySubscriber([
    'delay' => function($numRetries, \GuzzleHttp\Event\AbstractTransferEvent $event) {
        $waitSecs = $event->getResponse()->getHeader('Retry-After') ?: '5';
        return ($waitSecs * 1000) + 1000; // wait one second longer than the server said to
    },
    'filter' => \GuzzleHttp\Subscriber\Retry\RetrySubscriber::createStatusFilter(),
]);

// Manually create a Guzzle HTTP adapter
$guzzleAdapter = new \Phpoaipmh\HttpAdapter\GuzzleAdapter();
$guzzleAdapter->getGuzzleClient()->getEmitter()->attach($retrySubscriber);

$client  = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter);

这将创建一个客户端,当 OAI-PMH 端点发送 503 速率限制响应时,会自动重试请求。

发送任意查询参数

如果您希望与请求一起发送任意 HTTP 查询参数,您可以通过 \Phpoaipmh\Client 类发送它们

$client = new \Phpoaipmh\Client('http://some.service.com/oai');
$client->request('Identify', ['some' => 'extra-param']);

或者,如果您希望在利用 \Phpoaipmh\Endpoint 类的便利性的同时发送任意参数,您可以使用 Guzzle Param Middleware

首先,将中间件作为依赖项包含在您的应用程序中

$ composer require emarref/guzzle-param-middleware

然后,在加载 Phpoaipmh 库时,手动构建 Guzzle 客户端,并将中间件添加到堆栈中。示例

use Emarref\Guzzle\Middleware\ParamMiddleware
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;

// Setup the the Guzzle stack
$stack = HandlerStack()::create();
$stack->push(new ParamMiddleware(['api_key' => 'xyz123']));

// Setup Guzzle client, adapter, and PHP OAI-PMH client
$guzzleClient = new GuzzleClient(['handler' => $stack])
$guzzleAdapter = new \Phpoaipmh\HttpAdapter\GuzzleAdapter($guzzleClient)
$client  = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter);

这将向客户端的所有请求添加指定的查询参数。

使用 Guzzle v5 发送任意查询参数

如果您正在使用 Guzzle v5,可以使用 Guzzle 事件系统

// Create a function or class to add parameters to a request
$addParamsListener = function(\GuzzleHttp\Event\BeforeEvent $event) {
   $req = $event->getRequest();
   $req->getQuery()->add('api_key', 'xyz123');

   // You could do other things to the request here, too, like adding a header..
   $req->addHeader('Some-Header', 'some-header-value');
};

// Manually create a Guzzle HTTP adapter
$guzzleAdapter = new \Phpoaipmh\HttpAdapter\GuzzleAdapter();
$guzzleAdapter->getGuzzleClient()->getEmitter()->on('before', $addParamsListener);

$client  = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter);

实现技巧

从 OAI-PMH 端点收集数据可能是一个耗时的任务,尤其是在有大量记录的情况下。通常,这类任务通过 CLI 脚本或长时间运行的背景进程来完成。将其作为 Web 请求的一部分通常不是一个好主意。

鸣谢

许可

MIT 许可证;有关详细信息,请参阅 LICENSE 文件