caseyamcl / phpoaipmh
A PHP OAI-PMH 2.0 收割库
Requires
- php: >=5.5.0
- ext-simplexml: *
Requires (Dev)
- guzzlehttp/guzzle: ^6.0|^7.0
- jaschilz/php-coverage-badger: ^2.0
- mockery/mockery: ^0.9
- phpunit/phpunit: ^8.5
- squizlabs/php_codesniffer: ^3.5
- symfony/config: ^3.4|^4.3|^5.0
- symfony/console: ^3.4|^4.3|^5.0
- symfony/dependency-injection: ^3.4.26|^4.3|^5.0
- symfony/yaml: ^3.4|^4.3|^5.0
README
A PHP OAI-PMH 收割客户端库
此库提供了一个接口,可以从任何符合 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
对象的迭代器。
- 对于
identify
和getRecord
,返回一个SimpleXMLElement
对象 - 对于
listMetadataFormats
、listSets
、listIdentifiers
和listRecords
,返回一个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 文件