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个请求。如果您的代码在很短的时间内生成了过多的HTTP请求,这些端点将返回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端点的速率限制规则。
首先,将retry-subscriber作为依赖项包含在您的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 文件