Skip to content

Commit a9a2c29

Browse files
committed
Add records iterator
1 parent 6480735 commit a9a2c29

File tree

6 files changed

+354
-5
lines changed

6 files changed

+354
-5
lines changed

src/Client.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public function urlTo($cql, $start = 1, $count = 10)
101101
*
102102
* @return array
103103
*/
104-
protected function getHttpOptions()
104+
public function getHttpOptions()
105105
{
106106
$headers = array(
107107
'Accept' => 'application/xml'
@@ -123,7 +123,7 @@ protected function getHttpOptions()
123123

124124
/**
125125
* Perform a searchRetrieve request
126-
*
126+
*
127127
* @param string $cql
128128
* @param int $start Start value in result set (optional)
129129
* @param int $count Number of records to request (optional)
@@ -140,9 +140,20 @@ public function search($cql, $start = 1, $count = 10) {
140140
return new Response($body, $this);
141141
}
142142

143+
/**
144+
* Perform a searchRetrieve request and return an iterator over the records
145+
*
146+
* @param string $cql
147+
* @return Records
148+
*/
149+
public function records($cql)
150+
{
151+
return new Records($cql, $this);
152+
}
153+
143154
/**
144155
* Perform an explain request
145-
*
156+
*
146157
* @return ExplainResponse
147158
*/
148159
public function explain() {

src/InvalidResponseException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php namespace Scriptotek\Sru;
2+
3+
class InvalidResponseException extends \Exception{
4+
5+
public function __construct($message) {
6+
parent::__construct($message);
7+
}
8+
9+
}

src/Records.php

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php namespace Scriptotek\Sru;
2+
3+
use \Guzzle\Http\Client as HttpClient;
4+
5+
/**
6+
* When iterating, methods are called in the following order:
7+
*
8+
* rewind()
9+
* valid()
10+
* current()
11+
*
12+
* next()
13+
* valid()
14+
* current()
15+
*
16+
* ...
17+
*
18+
* next()
19+
* valid()
20+
*/
21+
class Records implements \Iterator {
22+
23+
/** @var HttpClient */
24+
protected $httpClient;
25+
26+
private $position;
27+
private $count;
28+
private $cql;
29+
private $client;
30+
private $lastResponse;
31+
32+
private $data = array();
33+
34+
/**
35+
* Create a new records iterator
36+
*
37+
* @param string $cql Query
38+
* @param Client $client SRU client reference (optional)
39+
* @param mixed $httpClient A http client
40+
* @param int $count Number of records to request per request
41+
*/
42+
public function __construct($cql, Client $client, $httpClient = null, $count = 10) {
43+
$this->position = 1;
44+
$this->count = $count; // number of records per request
45+
$this->cql = $cql;
46+
$this->httpClient = $httpClient ?: new HttpClient;
47+
$this->client = $client;
48+
}
49+
50+
/**
51+
* Return error message from last reponse, if any
52+
*/
53+
public function getError()
54+
{
55+
if (isset($this->lastResponse)) {
56+
return $this->lastResponse->error;
57+
}
58+
return null;
59+
}
60+
61+
/**
62+
* Fetch more records from the service
63+
*/
64+
private function fetchMore()
65+
{
66+
$url = $this->client->urlTo($this->cql, $this->position, $this->count);
67+
$options = $this->client->getHttpOptions();
68+
69+
$res = $this->httpClient->get($url, $options)->send();
70+
$body = $res->getBody(true);
71+
$this->lastResponse = new Response($body);
72+
$this->data = $this->lastResponse->records;
73+
74+
if (count($this->data) != 0 && $this->data[0]->position != $this->position) {
75+
throw new InvalidResponseException('Wrong index of first record in result set. '
76+
. 'Expected: ' .$this->position . ', got: ' . $this->data[0]->position
77+
);
78+
}
79+
}
80+
81+
/**
82+
* Rewind the Iterator to the first element
83+
*/
84+
function rewind() {
85+
$this->position = 1;
86+
$this->fetchMore();
87+
}
88+
89+
/**
90+
* Return the current element
91+
*
92+
* @return mixed
93+
*/
94+
function current() {
95+
return $this->data[0];
96+
}
97+
98+
/**
99+
* Return the key of the current element
100+
*
101+
* @return int
102+
*/
103+
function key() {
104+
return $this->position;
105+
}
106+
107+
/**
108+
* Move forward to next element
109+
*/
110+
function next() {
111+
112+
if (count($this->data) > 0) {
113+
array_shift($this->data);
114+
}
115+
++$this->position;
116+
117+
if (isset($this->lastResponse) && $this->position > $this->lastResponse->numberOfRecords) {
118+
return null;
119+
}
120+
121+
if (count($this->data) == 0) {
122+
$this->fetchMore();
123+
}
124+
125+
if (count($this->data) == 0) {
126+
return null;
127+
}
128+
129+
130+
}
131+
132+
/**
133+
* Check if current position is valid
134+
*
135+
* @return bool
136+
*/
137+
function valid() {
138+
return count($this->data) != 0;
139+
}
140+
141+
}

tests/ClientTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function testUrlTo()
3131

3232
public function testSearch()
3333
{
34-
$http = $this->basicHttpMock($this->simple_response);
34+
$http = $this->httpMockSingleResponse($this->simple_response);
3535
$sru = new Client($this->url, null, $http);
3636

3737
$this->assertXmlStringEqualsXmlString(

tests/RecordsTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php namespace Scriptotek\Sru;
2+
3+
use \Guzzle\Http\Message\Response as HttpResponse;
4+
use \Mockery as m;
5+
6+
class RecordsTest extends TestCase {
7+
8+
public function testIterating()
9+
{
10+
$cql = 'dummy';
11+
$uri = 'http://localhost';
12+
$n = 8;
13+
$response = $this->makeDummyResponse($n);
14+
$http = $this->httpMockSingleResponse($response);
15+
16+
$client = new Client($uri);
17+
$records = new Records($cql, $client, $http);
18+
$records->rewind();
19+
20+
$this->assertEquals(1, $records->key());
21+
$records->next();
22+
$records->next();
23+
$this->assertEquals(3, $records->key());
24+
$records->rewind();
25+
$this->assertEquals(1, $records->key());
26+
27+
$i = 0;
28+
foreach ($records as $rec) {
29+
$i++;
30+
}
31+
$this->assertEquals($n, $i);
32+
}
33+
34+
/**
35+
* @expectedException Scriptotek\Sru\InvalidResponseException
36+
*/
37+
public function testRepeatSameResponse()
38+
{
39+
$response = $this->makeDummyResponse(1);
40+
41+
$http = $this->httpMockSingleResponse($response);
42+
$uri = 'http://localhost';
43+
$cql = 'dummy';
44+
45+
$client = new Client($uri);
46+
$rec = new Records($cql, $client, $http);
47+
$rec->next();
48+
$rec->next();
49+
}
50+
51+
52+
public function testMultipleRequests()
53+
{
54+
$nrecs = 5;
55+
56+
$responses = array(
57+
$this->makeDummyResponse($nrecs, array('startRecord' => 1, 'maxRecords' => 2)),
58+
$this->makeDummyResponse($nrecs, array('startRecord' => 3, 'maxRecords' => 2)),
59+
$this->makeDummyResponse($nrecs, array('startRecord' => 5, 'maxRecords' => 2))
60+
);
61+
62+
$http = $this->httpMockListResponse($responses);
63+
$uri = 'http://localhost';
64+
$cql = 'dummy';
65+
66+
$client = new Client($uri);
67+
$records = new Records($cql, $client, $http);
68+
69+
$records->rewind();
70+
foreach (range(1, $nrecs) as $n) {
71+
$this->assertEquals($n, $records->key());
72+
$this->assertTrue($records->valid());
73+
$this->assertEquals($n, $records->current()->position);
74+
$records->next();
75+
}
76+
$this->assertFalse($records->valid());
77+
}
78+
79+
}

0 commit comments

Comments
 (0)